c语言 char转int_图文并茂,一文讲透C语言结构体内存对齐
↑點擊上方藍色字體,關注“嵌入式軟件實戰派”獲得更多精品干貨。
(以下有約5000字內容,建議收藏再讀,推薦下載源碼自行測試以加深理解。)
面試官:你知道C語言的結構體對齊嗎??
應聘者:聽說過……平時很少關注?
……
面試官:好吧,那回去等通知吧?
C語言結構體對齊問題,是面試必備問題。本文,除了用圖解的方式講清楚結構體知識點外,還將為你解答以下問題:- 為什么會有結構體內存對齊?
- 結構體怎么對齊?
- 學習結構體對齊有什么用?
- 結構體對齊有沒有實際應用?
▍結構體內存對齊的原因一句話,為了提高效率,這個跟芯片設計有關。自從我們剛學習編程開始,就會接觸到例如字、雙字、四字等概念這里涉及到內存邊界問題,它們的地址分別是可被2/4/8整除的。另外,在匯編中,不同長度的內存訪問會用到不同的匯編指令。如果,一塊內存在地址上隨便放的,CPU有可能就會用到多條指令來訪問,這就會降低效率。對于32位系統,如下圖的A可能需要2條指令訪問,而B只需1條指令。
▍結構體內存對齊的規則1. C語言基本類型的大小不要瞎猜,直接上代碼。每個平臺都不一樣,請讀者自行測試,以下我是基于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)
不管什么類型,指針都是相同大小的
每個特定平臺上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。
網上流傳一個表:
平臺 | 長度/模數 | 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 |
長度/模數 | 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 |
那么,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元素不能獲得其地址(即程序中不能通過&取址)
▍編程為什么要關注結構體內存對齊也許你會問,結構體愛怎么對齊就怎么對齊,我管它干嘛!1. 節省內存在嵌入式軟件開發中,特別是內存資源匱乏的小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,一放大,就有很明顯的區別了。2. union的內存對齊需要對于同一個內存,有時為了滿足不同的訪問形式,定義一個聯合體變量,或者一個結構體和聯合體組合的變量。此時就要知道其內存結構是怎么分布的。3. 內存拷貝
有時候,我們在通信數據接收處理時候,往往遇到,數組和結構體的搭配。
即,通信時候,通常使用數組參數形式接收,而處理的時候,按照預定義格式去訪問處理。例如:
U8 comm_data[10];typedef?struct{ U8 id; U16 len; U8 data[6];}FRAME;FRAME* pFram = (FRAME*)comm_data;此處,必須要理解這個FRAM的內存結構是怎么樣的對齊規則。4. 調試仿真時看壓棧數據在調試某些奇葩問題時,迫不得已,我們會研究函數跳轉或者線程切換時的棧數據,遇到結構體內容,肯定要懂得其內存對齊方式才能更好地獲得棧內信息。當然,還有其他方面的原因,在此就不一一列舉了。▍結構體內存對齊實際應用上面一個章節已經部分講到這個結構體內存對齊的應用了,例如通信數據的處理等。另外,再舉兩個例子:1. 內存的mapping假設你要做一個燒錄文件,你想往文件頭空間128個字節內放一段項目信息(例如程序大小、CRC校驗碼、其他項目信息等)。第一反應,你會考慮用一個結構體,定義一段這樣的數據,程序運行的時候也定義同樣的結構體去讀取這個內存。但是你需要知道結構體大小啊,這個結構體內存對齊的規則還是需要了解的。2. 單片機寄存器的mapping在寫MCU驅動的時候,訪問寄存器的方式有很多種,但是做到清晰明了,適配性好的,往往需要諸多考量。直接通過整型指針指到特定地址去訪問,是沒有問題的,但是對于某一類型的寄存器,往往不是一個固定地址,其后面還有一堆子寄存器屬性需要配置。每個地址都通過整型指針訪問,那就很多很凌亂。我們可以通過定義一個特定的結構體,用其指針直接mapping到寄存器的base地址。但是遇到有些地址是空的怎么辦?甚至有些寄存器是32位的,有些16位,甚至8位的,各種參差不齊都在里面。那就要考慮結構體內存對齊了,特別是結構體內有不同類型的元素。這里只探討應用場景,具體實現還要根據實際情況來定義。
▍測試源碼
篇幅有限,此處不貼完整的源碼了。
如果想要獲取源碼,關注公眾號,回復"struct"即可獲得下載鏈接。
往期精彩內容推薦>>>
孔乙己:main函數有四樣寫法
基于C99規范,最全C語言預處理知識總結
圖解棧(Stack)與隊列(Queue)
總結
以上是生活随笔為你收集整理的c语言 char转int_图文并茂,一文讲透C语言结构体内存对齐的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 烤瓷牙摘掉还能再按上吗
- 下一篇: android 判断文件是否存在_每日一