命名空间
变体
操作

结构体声明

来自 cppreference.com
< c‎ | 语言

结构体是一种类型,它由一系列成员组成,这些成员的存储空间按顺序分配(与联合体相反,联合体是一种类型,它由一系列成员组成,这些成员的存储空间是重叠的)。

结构体的 类型说明符union 类型说明符相同,除了使用的关键字不同

内容

[编辑] 语法

struct attr-spec-seq (optional) name (optional) { struct-declaration-list } (1)
struct attr-spec-seq (optional) name (2)
1) 结构体定义:引入新的类型结构体 name 并定义其含义
2) 如果在单独的一行上使用,如 struct name ;,则声明但不定义结构体 name(参见下面的前向声明)。在其他上下文中,指定先前声明的结构体,并且 attr-spec-seq 不允许。
name - 正在定义的结构体的名称
struct-declaration-list - 任意数量的变量声明、 位域 声明和 静态断言 声明。不允许具有不完整类型的成员和具有函数类型的成员(除了下面描述的灵活数组成员)
attr-spec-seq - (C23)可选的 属性 列表,应用于结构体类型

[编辑] 解释

在结构体对象中,其元素的地址(以及位域分配单元的地址)按成员定义的顺序递增。指向结构体的指针可以强制转换为指向其第一个成员的指针(或者,如果成员是位域,则强制转换为指向其分配单元的指针)。同样,指向结构体第一个成员的指针可以强制转换为指向封闭结构体的指针。在结构体的任意两个成员之间或最后一个成员之后,可能存在未命名的填充,但在第一个成员之前不存在。结构体的大小至少与其成员大小的总和一样大。

如果结构体定义了至少一个命名成员,则它还可以声明其最后一个成员具有不完整的数组类型。当访问灵活数组成员的元素(在使用运算符 .-> 且灵活数组成员的名称作为右侧操作数的表达式中)时,则结构体行为就像数组成员具有适合为该对象分配的内存的最长大小一样。如果没有分配额外的存储空间,则它行为就像具有 1 个元素的数组一样,只是如果访问该元素或生成指向该元素之后一个位置的指针,则行为未定义。初始化和赋值运算符会忽略灵活数组成员。 sizeof 会忽略它,但可能具有比忽略会暗示的更多的尾部填充。具有灵活数组成员的结构体(或具有递归可能结构成员的联合体,该成员具有灵活数组成员)不能作为数组元素或其他结构体的成员出现。

struct s { int n; double d[]; }; // s.d is a flexible array member
 
struct s t1 = { 0 };          // OK, d is as if double d[1], but UB to access
struct s t2 = { 1, { 4.2 } }; // error: initialization ignores flexible array
 
// if sizeof (double) == 8
struct s *s1 = malloc(sizeof (struct s) + 64); // as if d was double d[8]
struct s *s2 = malloc(sizeof (struct s) + 40); // as if d was double d[5]
 
s1 = malloc(sizeof (struct s) + 10); // now as if d was double d[1]. Two bytes excess.
double *dp = &(s1->d[0]);    // OK
*dp = 42;                    // OK
s1->d[1]++;                  // Undefined behavior. 2 excess bytes can't be accessed
                             // as double.
 
s2 = malloc(sizeof (struct s) + 6);  // same, but UB to access because 2 bytes are
                                     // missing to complete 1 double
dp = &(s2->d[0]);            // OK, can take address just fine
*dp = 42;                    // undefined behavior
 
*s1 = *s2; // only copies s.n, not any element of s.d
           // except those caught in sizeof (struct s)
(从 C99 开始)

类似于联合体,结构体的未命名成员,其类型是无 name 的结构体,被称为匿名结构体。匿名结构体的每个成员都被认为是封闭结构体或联合体的成员,保留其结构布局。如果封闭结构体或联合体也是匿名的,则这种情况会递归地应用。

struct v
{
   union // anonymous union
   {
      struct { int i, j; }; // anonymous structure
      struct { long k, l; } w;
   };
   int m;
} v1;
 
v1.i = 2;   // valid
v1.k = 3;   // invalid: inner structure is not anonymous
v1.w.k = 5; // valid

类似于联合体,如果结构体在没有任何命名成员的情况下定义(包括通过匿名嵌套结构体或联合体获得的成员),则程序的行为是未定义的。

(从 C11 开始)

[编辑] 前向声明

以下形式的声明

struct attr-spec-seq (optional) name ;

会隐藏在标记名称空间中对名称 name 的任何先前声明的含义,并在当前作用域中声明 name 作为新的结构体名称,该名称将在稍后定义。在定义出现之前,该结构体名称具有 不完整类型

这允许相互引用的结构体

struct y;
struct x { struct y *p; /* ... */ };
struct y { struct x *q; /* ... */ };

注意,新的结构体名称也可以通过在另一个声明中使用结构体标记来引入,但如果在标记 名称空间 中存在先前声明的具有相同名称的结构体,则该标记将引用该名称

struct s* p = NULL; // tag naming an unknown struct declares it
struct s { int a; }; // definition for the struct pointed to by p
void g(void)
{
    struct s; // forward declaration of a new, local struct s
              // this hides global struct s until the end of this block
    struct s *p;  // pointer to local struct s
                  // without the forward declaration above,
                  // this would point at the file-scope s
    struct s { char* p; }; // definitions of the local struct s
}

[编辑] 关键字

结构体

[编辑] 注意事项

有关结构体初始化器的规则,请参阅 结构体初始化

由于不允许具有不完整类型的成员,并且结构体类型在定义结束之前不完整,因此结构体不能具有其自身的类型作为成员。允许指向其自身类型的指针,并且通常用于实现链表或树中的节点。

由于结构体声明不会建立 作用域,因此在 struct-declaration-list 中的声明引入的嵌套类型、枚举和枚举器在定义结构体的周围作用域中是可见的。

[编辑] 示例

#include <stddef.h>
#include <stdio.h>
 
int main(void)
{
    // Declare the struct type.
    struct car
    {
        char* make;
        int year;
    };
    // Declare and initialize an object of a previously-declared struct type.
    struct car c = {.year = 1923, .make = "Nash"};
    printf("1) Car: %d %s\n", c.year, c.make);
 
    // Declare a struct type, an object of that type, and a pointer to it.
    struct spaceship
    {
        char* model;
        int max_speed;
    } ship = {"T-65 X-wing starfighter", 1050},
    *pship = &ship;
    printf("2) Spaceship: %s. Max speed: %d km/h\n\n", ship.model, ship.max_speed);
 
    // Address increase in order of definition. Padding may be inserted.
    struct A { char a; double b; char c; };
    printf(
        "3) Offset of char a = %zu\n"
        "4) Offset of double b = %zu\n"
        "5) Offset of char c = %zu\n"
        "6) Size of struct A = %zu\n\n",
        offsetof(struct A, a),
        offsetof(struct A, b),
        offsetof(struct A, c),
        sizeof(struct A)
    );
    struct B { char a; char b; double c; };
    printf(
        "7) Offset of char a = %zu\n"
        "8) Offset of char b = %zu\n"
        "9) Offset of double c = %zu\n"
        "A) Size of struct B = %zu\n\n",
        offsetof(struct B, a),
        offsetof(struct B, b),
        offsetof(struct B, c),
        sizeof(struct B)
    );
 
    // A pointer to a struct can be cast to a pointer
    // to its first member and vice versa.
    char** pmodel = (char **)pship;
    printf("B) %s\n", *pmodel);
    pship = (struct spaceship *)pmodel;
}

可能的输出

1) Car: 1923 Nash
2) Spaceship: T-65 X-wing starfighter. Max speed: 1050 km/h
 
3) Offset of char a = 0
4) Offset of double b = 8
5) Offset of char c = 16
6) Size of struct A = 24
 
7) Offset of char a = 0
8) Offset of char b = 1
9) Offset of double c = 8
A) Size of struct B = 16
 
B) T-65 X-wing starfighter

[编辑] 缺陷报告

以下行为更改缺陷报告已追溯应用于先前发布的 C 标准。

DR 应用于 已发布的行为 正确行为
DR 499 C11 匿名结构体/联合体的成员被认为是封闭结构体/联合体的成员 它们保留其内存布局

[编辑] 参考文献

  • C23 标准 (ISO/IEC 9899:2024)
  • 6.7.2.1 结构体和联合体说明符 (p: TBD)
  • C17 标准 (ISO/IEC 9899:2018)
  • 6.7.2.1 结构体和联合体说明符 (p: 81-84)
  • C11 标准 (ISO/IEC 9899:2011)
  • 6.7.2.1 结构体和联合体说明符 (p: 112-117)
  • C99 标准 (ISO/IEC 9899:1999)
  • 6.7.2.1 结构体和联合体说明符 (p: 101-104)
  • C89/C90 标准 (ISO/IEC 9899:1990)
  • 3.5.2.1 结构体和联合体说明符

[编辑] 另请参阅

C++ 文档 for 类声明