當(dāng)前位置:首頁 > 公眾號(hào)精選 > 嵌入式微處理器
[導(dǎo)讀]面試官:你知道C語言的結(jié)構(gòu)體對(duì)齊嗎? 應(yīng)聘者:聽說過……平時(shí)很少關(guān)注?…… 面試官:好吧,那回去等通知吧? C語言結(jié)構(gòu)體對(duì)齊問題,是面試必備問題。 本文,除了用圖解的方式講清楚結(jié)構(gòu)體知識(shí)點(diǎn)外,還將為你解答以下問題: 為什么會(huì)有結(jié)構(gòu)體內(nèi)存對(duì)齊? 結(jié)構(gòu)體

面試官:你知道C語言的結(jié)構(gòu)體對(duì)齊嗎?

應(yīng)聘者:聽說過……平時(shí)很少關(guān)注 ……

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


C語言結(jié)構(gòu)體對(duì)齊問題,是面試必備問題。
本文,除了用圖解的方式講清楚結(jié)構(gòu)體知識(shí)點(diǎn)外,還將為你解答以下問題:
  • 為什么會(huì)有結(jié)構(gòu)體內(nèi)存對(duì)齊?
  • 結(jié)構(gòu)體怎么對(duì)齊?
  • 學(xué)習(xí)結(jié)構(gòu)體對(duì)齊有什么用?
  • 結(jié)構(gòu)體對(duì)齊有沒有實(shí)際應(yīng)用?


▍結(jié)構(gòu)體內(nèi)存對(duì)齊的原因
一句話,為了提高效率,這個(gè)跟芯片設(shè)計(jì)有關(guān)。
自從我們剛學(xué)習(xí)編程開始,就會(huì)接觸到例如字、雙字、四字等概念這里涉及到內(nèi)存邊界問題,它們的地址分別是可被2/4/8整除的。另外,在匯編中,不同長度的內(nèi)存訪問會(huì)用到不同的匯編指令。
如果,一塊內(nèi)存在地址上隨便放的,CPU有可能就會(huì)用到多條指令來訪問,這就會(huì)降低效率。
對(duì)于32位系統(tǒng),如下圖的A可能需要2條指令訪問,而B只需1條指令。


▍結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則
1. C語言基本類型的大小
不要瞎猜,直接上代碼。 每個(gè)平臺(tái)都不一樣,請(qǐng)讀者自行測(cè)試,以下我是基于Windows上MinGW的GCC測(cè)的。
#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*);}
結(jié)果是:
 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
這些內(nèi)容不用記住,不同平臺(tái)是不一樣的,使用之前,一定要親自測(cè)試驗(yàn)證下,但是可以總結(jié)出以下信息:
  • void類型不是空的,占一個(gè)字節(jié)

  • long不一定比int大

  • C語言空結(jié)構(gòu)體的大小為0(注意:C++的為1)

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


2. C語言結(jié)構(gòu)體的內(nèi)存對(duì)齊
我先看個(gè)例子:
#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);
咦……這宏定義是啥意思?傳送門:《 基于C99規(guī)范,最全C語言預(yù)處理知識(shí)總結(jié)
輸出結(jié)果:
 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
結(jié)論1:一般情況下, 結(jié)構(gòu)體所占的內(nèi)存大小并非元素本身大小之和。
結(jié)論2: 不嚴(yán)謹(jǐn)?shù)?,結(jié)構(gòu)體內(nèi)存的大小按最大元素大小對(duì)齊。
繼續(xù)看例子:
 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);
輸出結(jié)果:
 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
出現(xiàn)問題了,你看s12和s14,sizeof(long long)應(yīng)該是12,按結(jié)論而推斷sizeof(s12)和sizeof(s13)應(yīng)該都是24。
這里跟平臺(tái)和編譯器的一個(gè)模數(shù)有關(guān)。
對(duì)結(jié)論2修正: 結(jié)構(gòu)體內(nèi)存大小應(yīng)按最大元素大小對(duì)齊,如果最大元素大小超過模數(shù),應(yīng)按模數(shù)大小對(duì)齊。
額外再送一條結(jié)論:如果結(jié)構(gòu)體的最大元素大小超過模數(shù), 結(jié)構(gòu)體的起始地址是可以被模數(shù)整除的。如果,最大元素大小沒有超過模數(shù)大小,那 它的起始地址是可以被最大元素大小整除。
那么,這個(gè)模數(shù)是什么?

每個(gè)特定平臺(tái)上的編譯器都有自己的默認(rèn)“對(duì)齊系數(shù)”(也叫對(duì)齊模數(shù))。

網(wǎng)上流傳一個(gè)表:

平臺(tái)

長度/模數(shù)

char

short

int

long

float

double

long long

long double

Win-32

長度

1

2

4

4

4

8

8

8

模數(shù)

1

2

4

4

4

8

8

8

Linux-32

長度

1

2

4

4

4

8

8

12

模數(shù)

1

2

4

4

4

4

4

4

Linux-64

長度

1

2

4

8

4

8

8

16

模數(shù)

1

2

4

8

4

8

8

16

本文的的例子我用的是MinGW32的GCC來測(cè)試,你猜符合上表的哪一項(xiàng)?
別急,再看一個(gè)例子:
 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);
結(jié)果是:
 s11 size = 16 s11.e_int addr: 0028FF18, offset: 0 s11 size = 16 s11.e_double addr: 0028FF20, offset: 8
很明顯,上表沒有一項(xiàng)完全對(duì)應(yīng)得上的。簡單匯總以下我測(cè)試的結(jié)果:

長度/模數(shù)

char

short

int

long

float

double

long long

long double

長度

1

2

4

4

4

8

8

12

模數(shù)

1

2

4

4

4

8

8

8

所以,再強(qiáng)調(diào)一下:因?yàn)榄h(huán)境的差異, 在你參考使用之前,請(qǐng)自行測(cè)試一下。
另外,提一下:這個(gè)模數(shù)是可以改變的,可以用預(yù)編譯命令 #pragma pack(n),n=1,2,4,8,16來改變這一系數(shù),其中的n就是你要指定的“對(duì)齊系數(shù)”。
例如
#pragma pack(1)typedef struct { char e_char; long double e_ld;}S14;#pragma pack()
#pragma是啥玩意?有興趣可以看看:《 基于C99規(guī)范,最全C語言預(yù)處理知識(shí)總結(jié)
好了,我們繼續(xù),這似乎沒啥技術(shù)含量,我們提升下難度:
 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;
你覺得這倆結(jié)構(gòu)體所占內(nèi)存是一樣大嗎?那你就錯(cuò)了:
 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
why?
上個(gè)圖先看看,它們內(nèi)存是怎么對(duì)齊的:

我們套一遍那幾條結(jié)論就可以知道:
理解 按最大元素大小或模數(shù)對(duì)齊,就可以看到S2的內(nèi)存分布;
對(duì)于S3,e_int的位置地址,肯定是要按int的大小對(duì)齊的(地址可被int大小整除),這樣才能提高訪問效率。同時(shí),這導(dǎo)致了很大的內(nèi)存浪費(fèi)。
以上例子,我們看到挨在一起的兩個(gè)char會(huì)放在同一個(gè)對(duì)齊單元,如果挨在一起的short和char會(huì)不會(huì)放一起?
 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);
輸出結(jié)果:
 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

得出一個(gè)經(jīng)驗(yàn):
我們?cè)诙x結(jié)構(gòu)體的時(shí)候,盡量把大小相同或相近的元素放一起,以減少結(jié)構(gòu)體占用的內(nèi)存空間。
再來一個(gè)問題:
結(jié)構(gòu)體套著另一個(gè)結(jié)構(gòu)體怎么計(jì)算?
 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);
輸出結(jié)果:
 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

得出結(jié)論:結(jié)構(gòu)體內(nèi)的結(jié)構(gòu)體,結(jié)構(gòu)體內(nèi)的元素并不會(huì)和結(jié)構(gòu)體外的元素合并占一個(gè)對(duì)齊單元。
溫馨提示:大家不要刻意去記這些結(jié)論,動(dòng)手去試試并思考下效果會(huì)更好。

3. 聯(lián)合體union的內(nèi)存對(duì)齊
直接上代碼:
 typedef union  { char e_char; int e_int; }U1;
U1 u1; STRUCT_E_ADDR(u1, e_char); STRUCT_E_ADDR(u1, e_int);
輸出結(jié)果:
 u1 size = 4 u1.e_char addr: 0028FF2C u1 size = 4 u1.e_int addr: 0028FF2C
從教科書上,我都可以理解,聯(lián)合體里面的元素,實(shí)際上共享同一個(gè)空間。

那么,union跟struct結(jié)合呢?

 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

實(shí)際上跟結(jié)構(gòu)體類似,也沒有特別的規(guī)則。

順便提一下,使用union時(shí),要留意平臺(tái)的大小端問題。

大端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的低地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的高地址中,這樣的存儲(chǔ)模式有點(diǎn)兒類似于把數(shù)據(jù)當(dāng)作字符串順序處理:地址由小向大增加,而數(shù)據(jù)從高位往低位放;這和我們的閱讀習(xí)慣一致。 

小端模式,是指數(shù)據(jù)的高字節(jié)保存在內(nèi)存的高地址中,而數(shù)據(jù)的低字節(jié)保存在內(nèi)存的低地址中,這種存儲(chǔ)模式將地址的高低和數(shù)據(jù)位權(quán)有效地結(jié)合起來,高地址部分權(quán)值高,低地址部分權(quán)值低。

百度百科——大小端模式

怎么獲知自己使用的平臺(tái)的大小端?Linux有個(gè)方法:

 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)的相關(guān)

位域在本文沒什么好探討的,在結(jié)構(gòu)體對(duì)齊方面沒什么特別的地方。

直接看個(gè)測(cè)試代碼,就可以明白:

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);}

輸出結(jié)果是:

 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

有幾個(gè)點(diǎn)需要注意下:

  1. 內(nèi)存的計(jì)算單位是byte,不是bit

  2. 結(jié)構(gòu)體內(nèi)即使有bitfield元素,其對(duì)齊規(guī)則還是按照基本類型來

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


5. 規(guī)則總結(jié)
首先,不推薦記憶這些條條框框的文字,以下內(nèi)容僅供參考:
  1. 結(jié)構(gòu)體的內(nèi)存大小,并非其內(nèi)部元素大小之和;
  2. 結(jié)構(gòu)體變量的起始地址,可以被最大元素基本類型大小或者模數(shù)整除;
  3. 結(jié)構(gòu)體的內(nèi)存對(duì)齊,按照其內(nèi)部最大元素基本類型或者模數(shù)大小對(duì)齊;
  4. 模數(shù)在不同平臺(tái)值不一樣,也可通過#pragma pack(n)方式去改變;
  5. 如果空間地址允許,結(jié)構(gòu)體內(nèi)部元素會(huì)拼湊一起放在同一個(gè)對(duì)齊空間;
  6. 結(jié)構(gòu)體內(nèi)有結(jié)構(gòu)體變量元素,其結(jié)構(gòu)體并非展開后再對(duì)齊;
  7. union和bitfield變量也遵循結(jié)構(gòu)體內(nèi)存對(duì)齊原則。



▍編程為什么要關(guān)注結(jié)構(gòu)體內(nèi)存對(duì)齊
也許你會(huì)問,結(jié)構(gòu)體愛怎么對(duì)齊就怎么對(duì)齊,我管它干嘛!
1. 節(jié)省內(nèi)存
在嵌入式軟件開發(fā)中,特別是內(nèi)存資源匱乏的小MCU,這個(gè)尤為重要。如果優(yōu)化程序內(nèi)存,使得MCU可以選更小的型號(hào),對(duì)于大批量出貨的產(chǎn)品,可以帶來更高利潤。
也許你還還感覺不到,上段代碼:
 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,一放大,就有很明顯的區(qū)別了。
2. union的內(nèi)存對(duì)齊需要
對(duì)于同一個(gè)內(nèi)存,有時(shí)為了滿足不同的訪問形式,定義一個(gè)聯(lián)合體變量,或者一個(gè)結(jié)構(gòu)體和聯(lián)合體組合的變量。此時(shí)就要知道其內(nèi)存結(jié)構(gòu)是怎么分布的。
3. 內(nèi)存拷貝

有時(shí)候,我們?cè)谕ㄐ艛?shù)據(jù)接收處理時(shí)候,往往遇到,數(shù)組和結(jié)構(gòu)體的搭配。

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

U8 comm_data[10];typedef struct{ U8 id; U16 len; U8 data[6];}FRAME;
FRAME* pFram = (FRAME*)comm_data;
此處,必須要理解這個(gè)FRAM的內(nèi)存結(jié)構(gòu)是怎么樣的對(duì)齊規(guī)則。
4. 調(diào)試仿真時(shí)看壓棧數(shù)據(jù)
在調(diào)試某些奇葩問題時(shí),迫不得已,我們會(huì)研究函數(shù)跳轉(zhuǎn)或者線程切換時(shí)的棧數(shù)據(jù),遇到結(jié)構(gòu)體內(nèi)容,肯定要懂得其內(nèi)存對(duì)齊方式才能更好地獲得棧內(nèi)信息。

當(dāng)然,還有其他方面的原因,在此就不一一列舉了。


▍結(jié)構(gòu)體內(nèi)存對(duì)齊 實(shí)際應(yīng)用
上面一個(gè)章節(jié)已經(jīng)部分講到這個(gè)結(jié)構(gòu)體內(nèi)存對(duì)齊的應(yīng)用了,例如通信數(shù)據(jù)的處理等。另外,再舉兩個(gè)例子:
1. 內(nèi)存的mapping
假設(shè)你要做一個(gè)燒錄文件,你想往文件頭空間128個(gè)字節(jié)內(nèi)放一段項(xiàng)目信息(例如程序大小、CRC校驗(yàn)碼、其他項(xiàng)目信息等)。第一反應(yīng),你會(huì)考慮用一個(gè)結(jié)構(gòu)體,定義一段這樣的數(shù)據(jù),程序運(yùn)行的時(shí)候也定義同樣的結(jié)構(gòu)體去讀取這個(gè)內(nèi)存。但是你需要知道結(jié)構(gòu)體大小啊,這個(gè)結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則還是需要了解的。
2. 單片機(jī)寄存器的mapping
在寫MCU驅(qū)動(dòng)的時(shí)候,訪問寄存器的方式有很多種,但是做到清晰明了,適配性好的,往往需要諸多考量。
直接通過整型指針指到特定地址去訪問,是沒有問題的,但是對(duì)于某一類型的寄存器,往往不是一個(gè)固定地址,其后面還有一堆子寄存器屬性需要配置。每個(gè)地址都通過整型指針訪問,那就很多很凌亂。
我們可以通過定義一個(gè)特定的結(jié)構(gòu)體,用其指針直接mapping到寄存器的base地址。但是遇到有些地址是空的怎么辦?甚至有些寄存器是32位的,有些16位,甚至8位的,各種參差不齊都在里面。
那就要考慮結(jié)構(gòu)體內(nèi)存對(duì)齊了,特別是結(jié)構(gòu)體內(nèi)有不同類型的元素。

這里只探討應(yīng)用場(chǎng)景,具體實(shí)現(xiàn)還要根據(jù)實(shí)際情況來定義。



▍測(cè)試源碼


#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("%18s = %d\n", "sizeof("#t")", sizeof(t))#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; // STRUCT_E_OFFSET(sb1, bf1); // STRUCT_E_OFFSET(sb1, bf2); // STRUCT_E_OFFSET(sb1, bf3); // STRUCT_E_OFFSET(sb1, bf4); 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){ // base_type_size(); struct_type_size(); union_type_size(); bitfield_type_size();
return 0;}


-END-


本文授權(quán)轉(zhuǎn)載自公眾號(hào)“嵌入式軟件實(shí)戰(zhàn)派”,作者實(shí)戰(zhàn)派大師兄




推薦閱讀



【01】【收藏】大佬們都在用的結(jié)構(gòu)體進(jìn)階小技巧
【02】結(jié)構(gòu)體內(nèi)存對(duì)齊,這回給你徹底搞會(huì)!
【03】RAM較小的MCU必須會(huì)這個(gè)技巧!結(jié)構(gòu)體內(nèi)存對(duì)齊解析
【04】工程師:這道題80%初學(xué)者都沒做對(duì)!你確定搞懂結(jié)構(gòu)體內(nèi)存對(duì)齊了?
【05】C語言之結(jié)構(gòu)體就這樣被攻克了?。ń^對(duì)值得收藏的文章)


免責(zé)聲明:整理文章為傳播相關(guān)技術(shù),版權(quán)歸原作者所有,如有侵權(quán),請(qǐng)聯(lián)系刪除

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

嵌入式ARM

掃描二維碼,關(guān)注更多精彩內(nèi)容

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉