Namespaces
Variants

Struct declaration

From cppreference.net

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

结构体的 类型说明符 union 类型说明符完全相同,唯一的区别在于所使用的关键字:

目录

语法

struct 属性说明符序列  (可选) 名称  (可选) { 结构体声明列表 } (1)
struct 属性说明符序列  (可选) 名称 (2)
1) 结构体定义:引入新类型 struct 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)
(since C99)

与联合体类似,结构体中未命名的成员(其类型为无 名称 的结构体)被称为 匿名结构体 。匿名结构体的每个成员均被视为外层结构体或联合体的成员,同时保持其结构布局。若外层结构体或联合体同为匿名,此规则递归适用。

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 属性说明符序列  (可选) 名称 ;

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

这允许结构体相互引用:

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

注意,新的结构体名称也可以通过在其他声明中使用结构体标签来引入,但如果标签 命名空间 中已存在同名的先前声明的结构体,该标签将引用该名称

struct s* p = NULL; // 使用结构体标签声明未知结构体
struct s { int a; }; // p所指向结构体的定义
void g(void)
{
    struct s; // 新局部结构体s的前向声明
              // 这将隐藏全局结构体s直至当前块结束
    struct s *p;  // 指向局部结构体s的指针
                  // 若无上述前向声明
                  // 此指针将指向文件作用域的s
    struct s { char* p; }; // 局部结构体s的定义
}

关键词

struct

注释

请参阅 结构体初始化 了解有关结构体初始化器的规则。

由于不允许使用不完整类型的成员,且结构体类型直到定义结束才变得完整,因此结构体不能拥有其自身类型的成员。允许使用指向自身类型的指针,这通常用于实现链表或树中的节点。

由于结构体声明不会建立 作用域 ,在 struct-declaration-list 内部声明引入的嵌套类型、枚举和枚举项在定义该结构体的外围作用域中可见。

示例

#include <stddef.h>
#include <stdio.h>
int main(void)
{
    // 声明结构体类型
    struct car
    {
        char* make;
        int year;
    };
    // 声明并初始化先前声明的结构体类型的对象
    struct car c = {.year = 1923, .make = "Nash"};
    printf("1) Car: %d %s\n", c.year, c.make);
    // 声明结构体类型、该类型的对象及其指针
    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);
    // 地址按定义顺序递增,可能会插入填充字节
    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)
    );
    // 结构体指针可强制转换为指向其首成员的指针,反之亦然
    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 结构和联合说明符(页码:101-104)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 3.5.2.1 结构和联合说明符

另请参阅

C++ 文档 关于 类声明