命名空间
变体
操作

结构体声明

来自 cppreference.cn
< c‎ | 语言

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

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

目录

[编辑] 语法

struct attr-spec-seq (可选) name (可选) { struct-declaration-list } (1)
struct attr-spec-seq (可选) name (2)
1) 结构体定义:引入新类型 struct name 并定义其含义
2) 如果单独使用,如 struct name ;,则只声明但不定义结构体 name(参见下面的前向声明)。在其他上下文中,命名先前声明的结构体,并且不允许使用 attr-spec-seq
name - 正在定义的结构体的名称
struct-declaration-list - 任意数量的变量声明、位域声明和静态断言声明。不允许不完整类型成员和函数类型成员(除了下面描述的柔性数组成员)
属性说明序列 - (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 (可选) 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

[编辑] 注意

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

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

由于结构体声明不建立作用域,因此在 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 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++ 文档,关于类声明