名稱空間
變體
操作

結構體宣告

來自 cppreference.com
< 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++ 文件,關於類宣告