圖文並茂,一次搞定C語言結構體內存對齊!(包含完整源碼)

2021-03-02 嵌入式ARM

面試官:你知道C語言的結構體對齊嗎?

應聘者:聽說過……平時很少關注 ……

面試官:好吧,那回去等通知吧 

本文,除了用圖解的方式講清楚結構體知識點外,還將為你解答以下問題:自從我們剛學習編程開始,就會接觸到例如字、雙字、四字等概念這裡涉及到內存邊界問題,它們的地址分別是可被2/4/8整除的。另外,在彙編中,不同長度的內存訪問會用到不同的彙編指令。如果,一塊內存在地址上隨便放的,CPU有可能就會用到多條指令來訪問,這就會降低效率。對於32位系統,如下圖的A可能需要2條指令訪問,而B只需1條指令。

不要瞎猜,直接上代碼。每個平臺都不一樣,請讀者自行測試,以下我是基於Windows上MinGW的GCC測的。
#define BASE_TYPE_SIZE(t)   printf("%12s : %2d Byte%s\n", #t, sizeof(t), (sizeof(t))>1?"s":"")void base_type_size(void){    BASE_TYPE_SIZE(void);    BASE_TYPE_SIZE(char);    BASE_TYPE_SIZE(short);    BASE_TYPE_SIZE(int);    BASE_TYPE_SIZE(long);    BASE_TYPE_SIZE(long long);    BASE_TYPE_SIZE(float);    BASE_TYPE_SIZE(double);    BASE_TYPE_SIZE(long double);    BASE_TYPE_SIZE(void*);    BASE_TYPE_SIZE(char*);    BASE_TYPE_SIZE(int*);        typedef struct     {    }StructNull;    BASE_TYPE_SIZE(StructNull);    BASE_TYPE_SIZE(StructNull*);}

        void :  1 Byte        char :  1 Byte       short :  2 Bytes         int :  4 Bytes        long :  4 Bytes   long long :  8 Bytes       float :  4 Bytes      double :  8 Bytes long double : 12 Bytes       void* :  4 Bytes       char* :  4 Bytes        int* :  4 Bytes  StructNull :  0 Byte StructNull* :  4 Bytes

這些內容不用記住,不同平臺是不一樣的,使用之前,一定要親自測試驗證下,但是可以總結出以下信息:

void類型不是空的,佔一個字節

long不一定比int大

C語言空結構體的大小為0(注意:C++的為1)

不管什麼類型,指針都是相同大小的

#define offset(type, member)      (size_t)&(((type *)0)->member)#define STRUCT_E_ADDR(s,e)          printf("%5s size = %2d %16s addr: %p\n", #s, sizeof(s), #s"."#e, &s.e)#define STRUCT_E_OFFSET(s,e)        printf("%5s size = %2d %16s offset: %2d\n", #s, sizeof(s), #s"."#e, offset(__typeof__(s),e))#define STRUCT_E_ADDR_OFFSET(s,e)   printf("%5s size = %2d %16s addr: %p, offset: %2d\n", #s, sizeof(s), #s"."#e, &s.e, offset(__typeof__(s),e))
typedef struct { int e_int; char e_char;}S1;S1 s1;STRUCT_E_ADDR_OFFSET(s1, e_int);STRUCT_E_ADDR_OFFSET(s1, e_char);typedef struct { int e_int; double e_double;}S11;S11 s11; STRUCT_E_ADDR_OFFSET(s11, e_int);STRUCT_E_ADDR_OFFSET(s11, e_double);

   s1 size =  8         s1.e_int addr: 0028FF28, offset:  0   s1 size =  8        s1.e_char addr: 0028FF2C, offset:  4  s11 size = 16        s11.e_int addr: 0028FF18, offset:  0  s11 size = 16     s11.e_double addr: 0028FF20, offset:  8

結論1:一般情況下,結構體所佔的內存大小並非元素本身大小之和。
結論2:不嚴謹地,結構體內存的大小按最大元素大小對齊。
    typedef struct     {        int e_int;        long double e_ld;    }S12;
typedef struct { long long e_ll; long double e_ld; }S13;
typedef struct { char e_char; long double e_ld; }S14;
    S12 s12; S13 s13; S14 s14; STRUCT_E_ADDR_OFFSET(s12, e_int); STRUCT_E_ADDR_OFFSET(s12, e_ld); STRUCT_E_ADDR_OFFSET(s13, e_ll); STRUCT_E_ADDR_OFFSET(s13, e_ld); STRUCT_E_ADDR_OFFSET(s14, e_char); STRUCT_E_ADDR_OFFSET(s14, e_ld);

  s12 size = 16        s12.e_int addr: 0028FF08, offset:  0  s12 size = 16         s12.e_ld addr: 0028FF0C, offset:  4  s13 size = 24         s13.e_ll addr: 0028FEF0, offset:  0  s13 size = 24         s13.e_ld addr: 0028FEF8, offset:  8  s14 size = 16       s14.e_char addr: 0028FEE0, offset:  0  s14 size = 16         s14.e_ld addr: 0028FEE4, offset:  4

出現問題了,你看s12和s14,sizeof(long long)應該是12,按結論而推斷sizeof(s12)和sizeof(s13)應該都是24。
對結論2修正:結構體內存大小應按最大元素大小對齊,如果最大元素大小超過模數,應按模數大小對齊。
額外再送一條結論:如果結構體的最大元素大小超過模數,結構體的起始地址是可以被模數整除的。如果,最大元素大小沒有超過模數大小,那它的起始地址是可以被最大元素大小整除

每個特定平臺上的編譯器都有自己的默認「對齊係數」(也叫對齊模數)。

網上流傳一個表:

平臺

長度/模數

char

short

int

long

float

double

long long

long double

Win-32

長度

1

2

4

4

4

8

8

8

模數

1

2

4

4

4

8

8

8

Linux-32

長度

1

2

4

4

4

8

8

12

模數

1

2

4

4

4

4

4

4

Linux-64

長度

1

2

4

8

4

8

8

16

模數

1

2

4

8

4

8

8

16

本文的的例子我用的是MinGW32的GCC來測試,你猜符合上表的哪一項?
    typedef struct     {        int e_int;        double e_double;    }S11;    S11 s11;    STRUCT_E_ADDR_OFFSET(s11, e_int);    STRUCT_E_ADDR_OFFSET(s11, e_double);

  s11 size = 16        s11.e_int addr: 0028FF18, offset:  0  s11 size = 16     s11.e_double addr: 0028FF20, offset:  8

很明顯,上表沒有一項完全對應得上的。簡單匯總以下我測試的結果:

長度/模數

char

short

int

long

float

double

long long

long double

長度

1

2

4

4

4

8

8

12

模數

1

2

4

4

4

8

8

8

所以,再強調一下:因為環境的差異,在你參考使用之前,請自行測試一下。另外,提一下:這個模數是可以改變的,可以用預編譯命令#pragma pack(n),n=1,2,4,8,16來改變這一係數,其中的n就是你要指定的「對齊係數」。
#pragma pack(1)typedef struct {    char e_char;    long double e_ld;}S14;#pragma pack()

好了,我們繼續,這似乎沒啥技術含量,我們提升下難度:
    typedef struct     {        int e_int;        char e_char1;        char e_char2;    }S2;
typedef struct { char e_char1; int e_int; char e_char2; }S3;    S2 s2; S3 s3;

   s2 size =  8         s2.e_int addr: 0028FED4, offset:  0   s2 size =  8       s2.e_char1 addr: 0028FED8, offset:  4   s2 size =  8       s2.e_char2 addr: 0028FED9, offset:  5   s3 size = 12       s3.e_char1 addr: 0028FEC4, offset:  0   s3 size = 12         s3.e_int addr: 0028FEC8, offset:  4   s3 size = 12       s3.e_char2 addr: 0028FECC, offset:  8

理解按最大元素大小或模數對齊,就可以看到S2的內存分布;
對於S3,e_int的位置地址,肯定是要按int的大小對齊的(地址可被int大小整除),這樣才能提高訪問效率。同時,這導致了很大的內存浪費。
以上例子,我們看到挨在一起的兩個char會放在同一個對齊單元,如果挨在一起的short和char會不會放一起?
    typedef struct     {        char e_char1;        short e_short;        char e_char2;        int e_int;        char e_char3;    }S4;    S4 s4;    STRUCT_E_ADDR_OFFSET(s4, e_char1);    STRUCT_E_ADDR_OFFSET(s4, e_short);    STRUCT_E_ADDR_OFFSET(s4, e_char2);    STRUCT_E_ADDR_OFFSET(s4, e_int);    STRUCT_E_ADDR_OFFSET(s4, e_char3);

   s4 size = 16       s4.e_char1 addr: 0028FEB4, offset:  0   s4 size = 16       s4.e_short addr: 0028FEB6, offset:  2   s4 size = 16       s4.e_char2 addr: 0028FEB8, offset:  4   s4 size = 16         s4.e_int addr: 0028FEBC, offset:  8   s4 size = 16       s4.e_char3 addr: 0028FEC0, offset: 12

我們在定義結構體的時候,儘量把大小相同或相近的元素放一起,以減少結構體佔用的內存空間。
    typedef struct     {        int e_int;        char e_char;    }S1;   typedef struct     {        S1 e_s;        char e_char;    }SS1;
typedef struct { short e_short; char e_char; }S6;
typedef struct { S6 e_s; char e_char; }SS2;         SS1 ss1;    STRUCT_E_ADDR_OFFSET(ss1, e_s); STRUCT_E_ADDR_OFFSET(ss1, e_char);
SS2 ss2; STRUCT_E_ADDR_OFFSET(ss2, e_s); STRUCT_E_ADDR_OFFSET(ss2, e_char);

  ss1 size = 12          ss1.e_s addr: 0028FE94, offset:  0  ss1 size = 12       ss1.e_char addr: 0028FE9C, offset:  8  ss2 size =  6          ss2.e_s addr: 0028FE8E, offset:  0  ss2 size =  6       ss2.e_char addr: 0028FE92, offset:  4

得出結論:結構體內的結構體,結構體內的元素並不會和結構體外的元素合併佔一個對齊單元。
溫馨提示:大家不要刻意去記這些結論,動手去試試並思考下效果會更好。
    typedef union     {        char e_char;        int e_int;    }U1;
U1 u1; STRUCT_E_ADDR(u1, e_char); STRUCT_E_ADDR(u1, e_int);

   u1 size =  4        u1.e_char addr: 0028FF2C   u1 size =  4         u1.e_int addr: 0028FF2C

從教科書上,我都可以理解,聯合體裡面的元素,實際上共享同一個空間。

那麼,union跟struct結合呢?

    typedef struct    {        int e_int1;         union        {            char ue_chars[9];             int ue_int;        }u;        double e_double;         int e_int2;     }SU2;    SU2 su2;    STRUCT_E_ADDR_OFFSET(su2, e_int1);    STRUCT_E_ADDR_OFFSET(su2, u.ue_chars);    STRUCT_E_ADDR_OFFSET(su2, u.ue_int);    STRUCT_E_ADDR_OFFSET(su2, e_double);    STRUCT_E_ADDR_OFFSET(su2, e_int2)

輸出:

  su2 size = 32       su2.e_int1 addr: 0028FEF8, offset:  0  su2 size = 32   su2.u.ue_chars addr: 0028FEFC, offset:  4  su2 size = 32     su2.u.ue_int addr: 0028FEFC, offset:  4  su2 size = 32     su2.e_double addr: 0028FF08, offset: 16  su2 size = 32       su2.e_int2 addr: 0028FF10, offset: 24

實際上跟結構體類似,也沒有特別的規則。

順便提一下,使用union時,要留意平臺的大小端問題。

大端模式,是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。 

小端模式,是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,高地址部分權值高,低地址部分權值低。

怎麼獲知自己使用的平臺的大小端?Linux有個方法:

    static union {         char c[4];         unsigned long l;     } endian_test = { { 'l', '?', '?', 'b' } };    #define ENDIANNESS ((char)endian_test.l)
printf("ENDIANNESS: %c\n", ENDIANNESS);

4. 位域(Bitfield)的相關

位域在本文沒什麼好探討的,在結構體對齊方面沒什麼特別的地方。

直接看個測試代碼,就可以明白:

void bitfield_type_size(void){    typedef struct    {        char bf1:1;        char bf2:1;        char bf3:1;        char bf4:3;    }SB1;
typedef struct { char bf1:1; char bf2:1; char bf3:1; char bf4:7; }SB2;
typedef struct { char bf1:1; char bf2:1; char bf3:1; int bfint:1; }SB3;
typedef struct { char bf1:1; char bf2:1; int bfint:1; char bf3:1; }SB4;
SB1 sb1; SB2 sb2; SB3 sb3;    SB4 sb4; VAR_ADDR(sb1); VAR_ADDR(sb2); VAR_ADDR(sb3); VAR_ADDR(sb4); typedef struct { unsigned char bf1:1; unsigned char bf2:1; unsigned char bf3:1; unsigned char bf4:3; }SB11;
typedef union { SB11 sb1; unsigned char e_char; }UB1; UB1 ub1;
STRUCT_E_ADDR_OFFSET(ub1, sb1); STRUCT_E_ADDR_OFFSET(ub1, e_char);
ub1.e_char = 0xF5; BITFIELD_VAL(ub1, e_char); BITFIELD_VAL(ub1, sb1.bf1); BITFIELD_VAL(ub1, sb1.bf2); BITFIELD_VAL(ub1, sb1.bf3); BITFIELD_VAL(ub1, sb1.bf4);}

輸出結果是:

  sb1 size =  1        sb1 addr: 0028FF2F  sb2 size =  2        sb2 addr: 0028FF2D  sb3 size =  8        sb3 addr: 0028FF24  sb4 size = 12        sb4 addr: 0028FF18  ub1 size =  1          ub1.sb1 addr: 0028FF17, offset:  0  ub1 size =  1       ub1.e_char addr: 0028FF17, offset:  0         ub1 :  1 Byte, ub1.e_char=0xF5         ub1 :  1 Byte, ub1.sb1.bf1=0x1         ub1 :  1 Byte, ub1.sb1.bf2=0x0         ub1 :  1 Byte, ub1.sb1.bf3=0x1         ub1 :  1 Byte, ub1.sb1.bf4=0x6

有幾個點需要注意下:

內存的計算單位是byte,不是bit

結構體內即使有bitfield元素,其對齊規則還是按照基本類型來

bitfield元素不能獲得其地址(即程序中不能通過&取址)

首先,不推薦記憶這些條條框框的文字,以下內容僅供參考:
結構體變量的起始地址,可以被最大元素基本類型大小或者模數整除;結構體的內存對齊,按照其內部最大元素基本類型或者模數大小對齊;模數在不同平臺值不一樣,也可通過#pragma pack(n)方式去改變;如果空間地址允許,結構體內部元素會拼湊一起放在同一個對齊空間;結構體內有結構體變量元素,其結構體並非展開後再對齊;union和bitfield變量也遵循結構體內存對齊原則。
也許你會問,結構體愛怎麼對齊就怎麼對齊,我管它幹嘛!
在嵌入式軟體開發中,特別是內存資源匱乏的小MCU,這個尤為重要。如果優化程序內存,使得MCU可以選更小的型號,對於大批量出貨的產品,可以帶來更高利潤。
    typedef struct     {        int e_int;        char e_char1;        char e_char2;    }S2;
typedef struct { char e_char1; int e_int; char e_char2; }S3; S2 s2[1024] = {0}; S3 s3[1024] = {0};

s2的大小為8K,而s3的大小為12K,一放大,就有很明顯的區別了。
對於同一個內存,有時為了滿足不同的訪問形式,定義一個聯合體變量,或者一個結構體和聯合體組合的變量。此時就要知道其內存結構是怎麼分布的。

有時候,我們在通信數據接收處理時候,往往遇到,數組和結構體的搭配。

即,通信時候,通常使用數組參數形式接收,而處理的時候,按照預定義格式去訪問處理。例如:

U8 comm_data[10];typedef struct{    U8 id;    U16 len;    U8 data[6];}FRAME;
FRAME* pFram = (FRAME*)comm_data;

此處,必須要理解這個FRAM的內存結構是怎麼樣的對齊規則。
在調試某些奇葩問題時,迫不得已,我們會研究函數跳轉或者線程切換時的棧數據,遇到結構體內容,肯定要懂得其內存對齊方式才能更好地獲得棧內信息。上面一個章節已經部分講到這個結構體內存對齊的應用了,例如通信數據的處理等。另外,再舉兩個例子:假設你要做一個燒錄文件,你想往文件頭空間128個字節內放一段項目信息(例如程序大小、CRC校驗碼、其他項目信息等)。第一反應,你會考慮用一個結構體,定義一段這樣的數據,程序運行的時候也定義同樣的結構體去讀取這個內存。但是你需要知道結構體大小啊,這個結構體內存對齊的規則還是需要了解的。在寫MCU驅動的時候,訪問寄存器的方式有很多種,但是做到清晰明了,適配性好的,往往需要諸多考量。
直接通過整型指針指到特定地址去訪問,是沒有問題的,但是對於某一類型的寄存器,往往不是一個固定地址,其後面還有一堆子寄存器屬性需要配置。每個地址都通過整型指針訪問,那就很多很凌亂。我們可以通過定義一個特定的結構體,用其指針直接mapping到寄存器的base地址。但是遇到有些地址是空的怎麼辦?甚至有些寄存器是32位的,有些16位,甚至8位的,各種參差不齊都在裡面。那就要考慮結構體內存對齊了,特別是結構體內有不同類型的元素。

這裡只探討應用場景,具體實現還要根據實際情況來定義。
#include <stdio.h>
#define offset(type, member) (size_t)&(((type *)0)->member)
#define STRUCT_E_ADDR(s,e) printf("%5s size = %2d %16s addr: %p\n", #s, sizeof(s), #s"."#e, &s.e)#define STRUCT_E_OFFSET(s,e) printf("%5s size = %2d %16s offset: %2d\n", #s, sizeof(s), #s"."#e, offset(__typeof__(s),e))#define STRUCT_E_ADDR_OFFSET(s,e) printf("%5s size = %2d %16s addr: %p, offset: %2d\n", #s, sizeof(s), #s"."#e, &s.e, offset(__typeof__(s),e))#define VAR_ADDR(v) printf("%5s size = %2d %10s addr: %p\n", #v, sizeof(v), #v, &v)#define BASE_TYPE_SIZE(t) printf("%12s : %2d Byte%s\n", #t, sizeof(t), (sizeof(t))>1?"s":"")#define BITFIELD_VAL(s,e) printf("%12s : %2d Byte%s, %10s=0x%X\n", #s, sizeof(s), (sizeof(s))>1?"s":"", #s"."#e, s.e)
void base_type_size(void){ BASE_TYPE_SIZE(void); BASE_TYPE_SIZE(char); BASE_TYPE_SIZE(short); BASE_TYPE_SIZE(int); BASE_TYPE_SIZE(long); BASE_TYPE_SIZE(long long); BASE_TYPE_SIZE(float); BASE_TYPE_SIZE(double); BASE_TYPE_SIZE(long double); BASE_TYPE_SIZE(void*); BASE_TYPE_SIZE(char*); BASE_TYPE_SIZE(int*);}void struct_type_size(void){ typedef struct { }StructNull;
typedef struct { int e_int; char e_char; }S1; BASE_TYPE_SIZE(StructNull); BASE_TYPE_SIZE(StructNull*); S1 s1; STRUCT_E_ADDR_OFFSET(s1, e_int); STRUCT_E_ADDR_OFFSET(s1, e_char);
typedef struct { int e_int; double e_double; }S11;
typedef struct { int e_int; long double e_ld; }S12;
typedef struct { long long e_ll; long double e_ld; }S13;
typedef struct { char e_char; long double e_ld; }S14;
S11 s11; S12 s12; S13 s13; S14 s14; STRUCT_E_ADDR_OFFSET(s11, e_int); STRUCT_E_ADDR_OFFSET(s11, e_double); STRUCT_E_ADDR_OFFSET(s12, e_int); STRUCT_E_ADDR_OFFSET(s12, e_ld); STRUCT_E_ADDR_OFFSET(s13, e_ll); STRUCT_E_ADDR_OFFSET(s13, e_ld); STRUCT_E_ADDR_OFFSET(s14, e_char); STRUCT_E_ADDR_OFFSET(s14, e_ld);
typedef struct { int e_int; char e_char1; char e_char2; }S2;
typedef struct { char e_char1; int e_int; char e_char2; }S3; typedef struct { char e_char1; short e_short; char e_char2; int e_int; char e_char3; }S4; typedef struct { long long e_ll; int e_int; }S5;
typedef struct { S1 e_s; char e_char; }SS1;
typedef struct { short e_short; char e_char; }S6;
typedef struct { S6 e_s; char e_char; }SS2;

char var1; S2 s2; char var2; S3 s3; VAR_ADDR(var1); STRUCT_E_ADDR_OFFSET(s2, e_int); STRUCT_E_ADDR_OFFSET(s2, e_char1); STRUCT_E_ADDR_OFFSET(s2, e_char2); VAR_ADDR(var2); STRUCT_E_ADDR_OFFSET(s3, e_char1); STRUCT_E_ADDR_OFFSET(s3, e_int); STRUCT_E_ADDR_OFFSET(s3, e_char2); S4 s4; STRUCT_E_ADDR_OFFSET(s4, e_char1); STRUCT_E_ADDR_OFFSET(s4, e_short); STRUCT_E_ADDR_OFFSET(s4, e_char2); STRUCT_E_ADDR_OFFSET(s4, e_int); STRUCT_E_ADDR_OFFSET(s4, e_char3); S5 s5; STRUCT_E_ADDR_OFFSET(s5, e_ll); STRUCT_E_ADDR_OFFSET(s5, e_int); SS1 ss1; STRUCT_E_ADDR_OFFSET(ss1, e_s); STRUCT_E_ADDR_OFFSET(ss1, e_char);
SS2 ss2; STRUCT_E_ADDR_OFFSET(ss2, e_s); STRUCT_E_ADDR_OFFSET(ss2, e_char);}
void union_type_size(void){ typedef union { char e_char; int e_int; }U1;
U1 u1; STRUCT_E_ADDR_OFFSET(u1, e_char); STRUCT_E_ADDR_OFFSET(u1, e_int);
typedef struct { short e_short; union { char ue_chars[9]; int ue_int; }u; }SU1;
typedef struct { int e_int1; union { char ue_chars[9]; int ue_int; }u; double e_double; int e_int2; }SU2;
SU1 su1; SU2 su2; STRUCT_E_ADDR_OFFSET(su1, e_short); STRUCT_E_ADDR_OFFSET(su1, u.ue_chars); STRUCT_E_ADDR_OFFSET(su1, u.ue_int); STRUCT_E_ADDR_OFFSET(su2, e_int1); STRUCT_E_ADDR_OFFSET(su2, u.ue_chars); STRUCT_E_ADDR_OFFSET(su2, u.ue_int); STRUCT_E_ADDR_OFFSET(su2, e_double); STRUCT_E_ADDR_OFFSET(su2, e_int2);}
void bitfield_type_size(void){ typedef struct { char bf1:1; char bf2:1; char bf3:1; char bf4:3; }SB1;
typedef struct { char bf1:1; char bf2:1; char bf3:1; char bf4:7; }SB2;
typedef struct { char bf1:1; char bf2:1; char bf3:1; int bfint:1; }SB3;
typedef struct { char bf1:1; char bf2:1; int bfint:1; char bf3:1; }SB4;
SB1 sb1; SB2 sb2; SB3 sb3; SB4 sb4; VAR_ADDR(sb1); VAR_ADDR(sb2); VAR_ADDR(sb3); VAR_ADDR(sb4); typedef struct { unsigned char bf1:1; unsigned char bf2:1; unsigned char bf3:1; unsigned char bf4:3; }SB11;
typedef union { SB11 sb1; unsigned char e_char; }UB1; UB1 ub1;
STRUCT_E_ADDR_OFFSET(ub1, sb1); STRUCT_E_ADDR_OFFSET(ub1, e_char);
ub1.e_char = 0xF5; BITFIELD_VAL(ub1, e_char); BITFIELD_VAL(ub1, sb1.bf1); BITFIELD_VAL(ub1, sb1.bf2); BITFIELD_VAL(ub1, sb1.bf3); BITFIELD_VAL(ub1, sb1.bf4);
static union { char c[4]; unsigned long l; } endian_test = { { 'l', '?', '?', 'b' } }; #define ENDIANNESS ((char)endian_test.l)
printf("ENDIANNESS: %c\n", ENDIANNESS);
}int main(void){ struct_type_size(); union_type_size(); bitfield_type_size();
return 0;}

-END-


本文授權轉載自公眾號「嵌入式軟體實戰派」,作者實戰派大師兄

免責聲明:整理文章為傳播相關技術,版權歸原作者所有,如有侵權,請聯繫刪除

相關焦點

  • 結構體字節對齊詳解(C/C++)
    開篇明義,在討論結構體字節對齊問題前,我們需知道,什麼是結構體,大家可以把結構體比喻成一籮筐,這個籮筐會隨著裡面放的東西的體積的增加而變大
  • C語言的那些小秘密之字節對齊
    本文引用地址:http://www.eepw.com.cn/article/272734.htm  可能看了上面的講解你還是不太明白,那我們再來看一次什麼是字節對齊呢?我們現在的計算機中內存空間都是按照字節來進行劃分的,從理論上來講的話似乎對任何類型的變量的訪問可以從任何地址開始,然而值得注意的就是,實際情況下在訪問特定變量的時候經常在特定的內存地址訪問,從而就需要各種類型的數據按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
  • 面試常考,項目易錯,長文詳解C/C++中的字節對齊
    對於內存對齊問題,主要存在於struct和union等複合結構在內存中的分布情況,許多實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們要求這些數據的首地址的值是某個數M(通常是4或8);對於內存對齊,主要是為了提高程序的性能,數據結構,特別是棧,應儘可能在自然邊界上對齊,經過對齊後,cpu的內存訪問速度大大提升。
  • C語言結構體(Struct)
    在C語言中,可以使用結構體(Struct)來存放一組不同類型的數據。結構體的定義形式為:struct 結構體名{    結構體所包含的變量或數組};結構體是一種集合,它裡面包含了多個變量或數組,它們的類型可以相同,也可以不同,每個這樣的變量或數組都稱為結構體的成員(Member)。
  • C語言結構體(struct)最全的講解(萬字乾貨)
    在C語言中,可以定義結構體類型,將多個相關的變量包裝成為一個整體使用。在結構體中的變量,可以是相同、部分相同,或完全不同的數據類型。在C語言中,結構體不能包含函數。在面向對象的程序設計中,對象具有狀態(屬性)和行為,狀態保存在成員變量中,行為通過成員方法(函數)來實現。C語言中的結構體只能描述一個對象的狀態,不能描述一個對象的行為。
  • C語言中的結構體和共用體(聯合體)
    (num)、姓名(name)、性別(sex)、年齡(age)、成績(score)、地址(addr)等,如下圖所示:如果要表示圖中的數據結構,但 C 語言並沒有提供這種現成的數據類型,因此我們需要用定義一種結構體類型來表示。
  • C 語言中的結構體和共用體(聯合體)
    簡單地說,我們可以把「結構體類型」和「結構體變量」理解為是面向對象語言中「類」和「對象」的概念。此外,結構體裡的成員也可以是一個結構體變量。結構體數組中各元素在內存中也是連續存放的,如下圖:共用體變量所佔的內存長度等於最長的成員的長度。例如上述定義的共用體變量 a, b, c 各佔 4 個字節(因為其中最長的實型變量佔 4 個字節),而不是各佔 2+1+4=7 個字節。
  • 為什麼C語言中的結構體的size,並不等於它所有成員size之和?
    解析程序輸出的結果與我們的預期不一致,原因在於「對齊機制」。如果將結構體 x 看作是一個容器,鑑於成員 s,i,c 的長度參差不齊,C語言編譯器不得不「填充」一些額外的空間,以滿足「對齊機制」。以上面的結構體 x 為例,初學者可能會認為它的成員在內存中的布局如下:
  • 定義只有一個數組成員的C語言結構體有什麼用?
    而如果將數組封裝在結構體內部,將結構體作為參數,那麼在函數內部,我們依然可以獲得完整的數組,請看下面的C語言代碼示例:#include <stdio.h>typedef struct{char arr[16];
  • 內存對齊 | 原來欄位順序還能影響結構體佔用的內存空間
    既然知道了 Go 編譯器在對結構體進行內存對齊的時候會在欄位之間留下內存空洞,那麼我們把只需要 1 個字節對齊的欄位 C 放在需要 8 個字節內存對齊的欄位 B 前面就能讓結構體 ST1 少佔 8 個字節。下面我們把 ST1 的 C 欄位放在 B 的前面再觀察一下 ST1 結構體的大小。
  • C語言中三塊「難啃的硬骨頭」
    C語言之所以被很多高手所喜歡,就是指針的魅力,中間可以靈活的切換,執行效率超高,這點也是讓小白暈菜的地方。指針是學習繞不過去的知識點,而且學完C語言,下一步緊接著切換到數據結構和算法,指針是切換的重點,指針搞不定下一步進行起來就很難,會讓很多人放棄繼續學習的勇氣。指針直接對接內存結構,常見的C語言裡面的指針亂指,數組越界根本原因就是內存問題。在指針這個點有無窮無盡的發揮空間。
  • C語言系列(十一):共用體與枚舉類型
    定義的共用體變量中,可以存放不同類型的數據,即不同類型的數據可以共用一個共用體空間,這些不同類型的數據項在內存中所佔用的起始單元相同。枚舉類型是用標識符表示的整數常量集合,枚舉常量是自動設置值的符號常量,從其作用上看,一方面,可以限制枚舉變量的取值範圍;另一方面,枚舉量的使用提高程序描述問題的直觀性。在C語言中,共用體可以使若干個類型相同或者不同的成員變量佔用同一段內存空間。
  • C語言編程核心要點
    類型C是強類型語言,有short、long、int、char、float、double等build-in數據類型,類型是貫穿c語言整個課程的核心概念。struct、union、enum屬於c的構造類型,用於自定義類型,擴充類型系統。變量變量用來保存數據,數據是操作的對象,變量的變字意味著它可以在運行時被修改。
  • 刻意練習—第九節《LInux c結構體&共用體&枚舉》
    >3.結構體的對齊訪問24.結構體的對齊訪問35.offsetof宏與container_of宏6.共用體union7.大小端模式18.大小端模式29.枚舉第二部分、章節介紹2.1.C語言之結構體概述 本節概述結構體,主要講了結構體和數組的差異
  • 【C語言入門】C語言的組成結構(基礎完整篇)!
    C是一種具有模塊化設計的命令式程式語言,具有簡約、直觀的設計風格,與相對清晰、簡單的語言結構。
  • 剖析c語言結構體的高級用法(二)
    昨天分享了結構體裡面的一些常見用法(因為測試代碼測試的有點晚,有些地方沒有分享完。),今天我們來繼續分享結構體裡面的其他用法。
  • C語言函數調用過程中的內存變化解析
    這個調用不是指C 語言上的函數調用的語法,而是在內存的視角下,函數的調用過程。本文將從C 語言調用實例,內存視角,反彙編代碼來探討C 語言函數的調用過程,也可以說是C 語言函數調用過程圖解。通過這個C 語言函數調用過程圖解,同學們將會知道,C 語言函數在調用時,內存空間是怎樣變化的。 要想理解這一個過程還好涉及到函數棧幀的概念。
  • 【C語言經典編程技術】C語言中union與struct的區別及sizeof的計算方式
    C語言不久的朋友對此非常困惑,下面我將簡單談一下自己對union與struct之間的區別聯 合(union) 1.union A{   int a[5];   char b;   double c;   }Air;表示聲明了一個名稱為A的聯合,可以使用A  variable 來定義聯合變量。在聯合變量 variable中 整型和字符型以及double型共用同一內存位置。
  • C語言編程 — 結構體與位域
    C 語言中,像 int、float、char 等是由 C 語言本身提供的數據類型,不能再進行分拆,我們稱之為基本數據類型。而結構體則是一種複雜數據類型(構造數據類型),它由程式設計師自己定義,由一系列具有相同或不同數據類型的變量組成。我們可以使用結構體表示更加複雜的數據類型。
  • C 語言面向對象編程 - 繼承
    上一篇文章的具體內容,可以查看以下連結: C 語言面向對象編程 - 封裝本篇文章繼續來討論一下,如何使用 C 語言實現面向對象編程的一個重要特性:繼承。繼承就是基於一個已有的類(一般稱作父類或基類),再去重新聲明或創建一個新的類,這個類可以稱為子類或派生類。子類或派生類可以訪問父類的數據和函數,然後子類裡面又添加了自己的屬性和數據。