顶级C程序员之路
?hi,大家好,我是極客君,今天分享一篇幾年前在CSDN上發(fā)表的文章。C語(yǔ)言是我非常喜歡的語(yǔ)言,也是眾多高級(jí)語(yǔ)言的鼻祖。
C語(yǔ)言的優(yōu)勢(shì):
門檻低:核心特性和標(biāo)準(zhǔn)庫(kù)都比較簡(jiǎn)單;
上限高:語(yǔ)法精簡(jiǎn),編程自由度高,可以玩出各種高端操作;
性能強(qiáng):極少抽象, 幾乎完全對(duì)應(yīng)到匯編實(shí)現(xiàn),性能強(qiáng),首選底層系統(tǒng)開發(fā)語(yǔ)言。
TIOBE編程社區(qū)揭曉了各大編程語(yǔ)言的排行情況:
Google搜索排行榜:
知乎熱門討論:
大量重量級(jí)軟件都是C寫的,比如Linux,UNIX,IOS內(nèi)核,windows內(nèi)核,Android內(nèi)核,jvm虛擬機(jī),CPython,Nginx,Redis,MySQL,GCC,GDB等等,可以看到,整個(gè)世界的基礎(chǔ)架構(gòu)都是在 C 語(yǔ)言之上運(yùn)行的,C語(yǔ)言是你必須學(xué)習(xí)的語(yǔ)言。因?yàn)檫@個(gè)世界上絕大多數(shù)編程語(yǔ)言都是 C-like 的語(yǔ)言,也是在不同的方面來(lái)解決 C 語(yǔ)言的各種問(wèn)題。學(xué)習(xí) C 語(yǔ)言是一個(gè)合格的程序員的必經(jīng)之路。
下面章節(jié)進(jìn)入今天的主題
深入理解字節(jié),字節(jié)序與字節(jié)對(duì)齊?
? ? ??? ? ? ? ? ? ? ? ?
一 總述
??
作為一個(gè)職業(yè)的coder玩家,首先應(yīng)該對(duì)計(jì)算機(jī)的字節(jié)有所了解。我們經(jīng)常談到的2進(jìn)制流,字節(jié)(字符)流,數(shù)據(jù)類型流(針對(duì)編程),結(jié)構(gòu)流等說(shuō)法,2進(jìn)制流,0和1的操作,屬于cpu級(jí),從字符流向上都是我們玩家關(guān)心,字節(jié)流屬于操作系統(tǒng)級(jí),今天談的就是字節(jié)流操作。
二? 字節(jié)
???
因?yàn)橛?jì)算機(jī)用二進(jìn)制,所以希望基本存儲(chǔ)單位的是2的n次方(和硬件設(shè)計(jì)有關(guān))。這樣讀取字節(jié)的時(shí)候,開銷不會(huì)太高,可以達(dá)到最大性能,因?yàn)閯傞_始,計(jì)算機(jī)是美國(guó)發(fā)明的,西文字符(英文字母大小寫,數(shù)字,其他特殊字符等將近有1百多個(gè),所以用7位來(lái)表示,這樣可以把所有西文字符表達(dá)完,再加上一位校檢位,一共8位,由于ASCⅡ的廣泛應(yīng)用,所有后來(lái),一個(gè)字節(jié)占8位就成了國(guó)際規(guī)定的標(biāo)準(zhǔn)了,一直沿用至今(有待研究,但不是今天的主題)。
???
一個(gè)字節(jié)占8個(gè)2進(jìn)制位,數(shù)據(jù)類型流,就是在c語(yǔ)言里面的數(shù)據(jù)類型占多少個(gè)字節(jié)。然后直接操作數(shù)據(jù)類型。在目前的32位系統(tǒng)中,c語(yǔ)言的基本數(shù)據(jù)類型有以下幾種:
Char??占一個(gè)字節(jié)?(-2^7?-?2^7-1?,-128?到?127)最高位?為符號(hào)位
Unsigned?char?占一個(gè)字節(jié)???(0-2^8,0到255)
Short???占2個(gè)字節(jié)?(-2^15?-?2^15-1?,?-32768到32767)最高位?為符號(hào)位
Unsigned?short?占2個(gè)字節(jié)?(0?-?2^16?,?0到65536)
Int?(字長(zhǎng),對(duì)于32位機(jī)為32位,16位機(jī)為16位,長(zhǎng)度不固定,和系統(tǒng)平臺(tái)有關(guān),處理器位數(shù)有關(guān),代表尋址空間),在32位機(jī)占4個(gè)字節(jié)(-2^31-2^31?)最高位?為符號(hào)位
Unsigned??int?占4個(gè)字節(jié)(0-2^32?)
Long?int?為4個(gè)字節(jié),在16,32位機(jī)都占4個(gè)字節(jié)(-2^31-2^31?)最高位?為符號(hào)位
Unsigned??long?int占4個(gè)字節(jié)(0-2^32?)
sizeof(short)?<=?sizeof(int)?<=?sizeof(long)??
在32位機(jī) int和long都是32位,沒(méi)有什么大的區(qū)別,但還是有些小的區(qū)別,有時(shí)最好用long,他大小固定,如果到其他平臺(tái),比如64位,他還是占4個(gè)字節(jié),而int卻占8個(gè)字節(jié),可以增加代碼的可移植性。
Long?long?int?占8個(gè)字節(jié)(c99標(biāo)準(zhǔn))??
Unsigned?long?long?int?占8個(gè)字節(jié)(c99標(biāo)準(zhǔn))??
浮點(diǎn)類型?由于浮點(diǎn)類型和整形的編碼不一樣,所以浮點(diǎn)型需要特殊分析。
Float?占4個(gè)字節(jié) ?
Double?占8個(gè)字節(jié)?
三 字節(jié)序
為什么有字節(jié)序這個(gè)概念存在呢?
不同的CPU有不同的字節(jié)序類型,這些字節(jié)序是指整數(shù)在內(nèi)存中保存的順序?這個(gè)叫做主機(jī)序 ,就是多個(gè)字節(jié)在內(nèi)存中擺放位置順序和解釋順序。
最常見(jiàn)的有兩種?:
1. Little endian:將低序字節(jié)存儲(chǔ)在起始地址? 4321
2. Big endian:將高序字節(jié)存儲(chǔ)在起始地址??? 1234
LE?little-endian?
最符合人的思維的字節(jié)序,地址低位存儲(chǔ)值的低位 ,地址高位存儲(chǔ)值的高位,怎么講是最符合人的思維的字節(jié)序,是因?yàn)閺娜说牡谝挥^感來(lái)說(shuō)低位值小,就應(yīng)該放在內(nèi)存地址小的地方,也即內(nèi)存地址低位,反之,高位值就應(yīng)該放在內(nèi)存地址大的地方,也即內(nèi)存地址高位。
BE?big-endian?
最直觀的字節(jié)序,地址低位存儲(chǔ)值的高位 ,地址高位存儲(chǔ)值的低位,為什么說(shuō)直觀,不要考慮對(duì)應(yīng)關(guān)系,只需要把內(nèi)存地址從左到右按照由低到高的順序?qū)懗?#xff0c;把值按照通常的高位到低位的順序?qū)懗鰞烧邔?duì)照,一個(gè)字節(jié)一個(gè)字節(jié)的填充進(jìn)去 。
例子:如果我們將0x1234abcd寫入到以0x0000開始的內(nèi)存中,則結(jié)果為
內(nèi)存地址? ? ? ? big-endian???little-endian
0x0000? ? ? ????0x12?????? ? ? ? ??0xcd
0x0001? ? ? ? ? 0x34? ? ? ? ? ? ? ?0xab
0x0002? ? ? ? ??0xab? ? ? ? ? ? ?? 0x34
0x0003? ? ? ? ? 0xcd? ? ? ? ? ? ? ?0x12
網(wǎng)絡(luò)字節(jié)序
網(wǎng)絡(luò)字節(jié)序是TCP/IP協(xié)議棧中規(guī)定好的一種數(shù)據(jù)表示格式,它與具體的CPU類型、操作系統(tǒng)等無(wú)關(guān),從而可以保證數(shù)據(jù)在不同主機(jī)之間傳輸時(shí)能夠被正確解釋。網(wǎng)絡(luò)字節(jié)順序采用big endian排序方式。
為了進(jìn)行轉(zhuǎn)換 bsd socket提供了轉(zhuǎn)換的函數(shù)?有下面四個(gè):
linux的源代碼(/include/netinet/in.h)
#?if?__BYTE_ORDER?==?__BIG_ENDIAN?
/*?The?host?byte?order?is?the?same?as?network?byte?order,?
???so?these?functions?are?all?just?identity.??*/?
#?define?ntohl(x)?(x)?
#?define?ntohs(x)?(x)?
#?define?htonl(x)?(x)?
#?define?htons(x)?(x)?
#?else?
#??if?__BYTE_ORDER?==?__LITTLE_ENDIAN?
#???define?ntohl(x)?__bswap_32?(x)?
#???define?ntohs(x)?__bswap_16?(x)?
#???define?htonl(x)?__bswap_32?(x)?
#???define?htons(x)?__bswap_16?(x)?
#??endif?
#?endif
htons?把unsigned?short類型從主機(jī)序轉(zhuǎn)換到網(wǎng)絡(luò)序
htonl?把unsigned?long類型從主機(jī)序轉(zhuǎn)換到網(wǎng)絡(luò)序
ntohs?把unsigned?short類型從網(wǎng)絡(luò)序轉(zhuǎn)換到主機(jī)序
ntohl?把unsigned?long類型從網(wǎng)絡(luò)序轉(zhuǎn)換到主機(jī)序
在使用little endian的系統(tǒng)中這些函數(shù)會(huì)把字節(jié)序進(jìn)行轉(zhuǎn)換, 在使用big endian類型的系統(tǒng)中這些函數(shù)會(huì)定義成空,什么都不做。
htonl(x)我簡(jiǎn)化為下:
?#define??htonl(x)??\??//連接符,連接下一行
??((unsigned?long?)?\
(?\
(((unsigned?long)(x)&0x000000ff<<24)|?\
(((unsigned?long)(x)&0x0000ff00)<<8)|?\
(((unsigned?long)(x)&0x00ff0000)>>8)|?\
(((unsigned?long)(x)&0xff000000)>>24)\
))
一般c語(yǔ)言編寫程序的字節(jié)序都是系統(tǒng)相關(guān)的,叫主機(jī)序,即指系統(tǒng)處理器本身所采用的字節(jié)序,java的字節(jié)碼是big-endian 和網(wǎng)絡(luò)字節(jié)序一樣,所以他和網(wǎng)絡(luò)通信不需要關(guān)心字節(jié)序問(wèn)題,如果要和其他平臺(tái)進(jìn)行通信,都要進(jìn)行字節(jié)序轉(zhuǎn)換,一般采用標(biāo)準(zhǔn)化的網(wǎng)絡(luò)序進(jìn)行傳輸:
發(fā)送端:主機(jī)序->網(wǎng)絡(luò)序
接收端:?網(wǎng)絡(luò)序->主機(jī)序
不管哪種字節(jié)序,目的都是大家對(duì)01二進(jìn)制串解釋都一樣,?不然兩方的解釋不一樣就可能會(huì)產(chǎn)生嚴(yán)重的問(wèn)題。
對(duì)于IP頭定義里面ihl和version字段需要考慮主機(jī)字節(jié)序;__be 表示big endian
網(wǎng)絡(luò)上流傳一個(gè)測(cè)試自己系統(tǒng)是什么字節(jié)序函數(shù)代碼:
byte_type get_sys_byte_order()
{
?? ? union? {
?? ? ? ? int ?b;
?? ? ? ? char a[4];
?? ? }U;
?? ? U.b = 0x01;
?? ? if(0x01 == U.a[0] )?{
?? ? ? ? return ? little_endian_type;
?? ? ?}else?{
?? ? ? ? return ? big_endian_type;
?? ? ?}?
}?
注:不同的CPU上運(yùn)行不同的操作系統(tǒng),字節(jié)序也是不同的,參見(jiàn)下表:
處理器? | ? 操作系統(tǒng) | ?字節(jié)序 |
Alpha? ? ? ? ? ?? | 全部 | ?Little?endian |
ARM | 全部 | ?Little?endian |
Intelx86 | 全部? ? ? | ?Little?endian? |
AMD | 全部? ? ? | ?Little?endian |
MIPS? ? ? ? ? ? ?? | NT? ? | ?Little?endian |
MIPS? ? ? ? ? ? ? | UNIX? ? ? | ?Big?endian? |
PowerPC? ? ? ? ? ? | NT? ? ?? | ?Little?endian |
PowerPC? ? ? ? ?? | 非NT??? ? | ?Big?endian? |
x86,AMD,ARM等芯片平臺(tái)是小端字節(jié)序系統(tǒng)
PowerPC?,PPC等芯片平臺(tái)是大端字節(jié)序系統(tǒng)
為什么不統(tǒng)一字節(jié)序?
1. 計(jì)算都是從低位開始的,因此計(jì)算機(jī)內(nèi)部處理采用小端序,效率較高。
2. 對(duì)于大端序,由于符號(hào)位在高位,因此對(duì)于數(shù)據(jù)正負(fù)或大小的判斷也就方便許多;其次,大端序也更符合人類的閱讀習(xí)慣。
3. 由于大小端各有優(yōu)劣,各個(gè)芯片廠商的堅(jiān)持自己設(shè)計(jì),字節(jié)序的問(wèn)題也就一直沒(méi)有統(tǒng)一。
字節(jié)序總結(jié)
不同處理器之間采用的字節(jié)序可能不同。
有些處理器的字節(jié)序是確定的,有些處理器的字節(jié)序是可配置的。
網(wǎng)絡(luò)序一般統(tǒng)一為大端序。
數(shù)據(jù)從本地傳輸?shù)骄W(wǎng)絡(luò),需要轉(zhuǎn)換為網(wǎng)絡(luò)序,接收到的網(wǎng)絡(luò)數(shù)據(jù)需要轉(zhuǎn)換為本地序后使用。
C提供了一組接口用于整型數(shù)據(jù)在本地序和網(wǎng)絡(luò)序之間的轉(zhuǎn)換。
多字節(jié)數(shù)據(jù)對(duì)象才需要轉(zhuǎn)字節(jié)序,例如int,short等,而char不需要。
由于處理器是按照IEEE標(biāo)準(zhǔn)處理float和double的,因此也不需要轉(zhuǎn)字節(jié)序。
由于Java虛擬機(jī)的存在,Java不需要考慮大小端的問(wèn)題。
四 字節(jié)對(duì)齊
首先我看哈程序的優(yōu)化種類:
Cpu級(jí)優(yōu)化((讀內(nèi)存),流水線,cache,現(xiàn)在多核等)->2進(jìn)制級(jí)(即01代碼)優(yōu)化(現(xiàn)在估計(jì)沒(méi)有人去做了)->匯編級(jí)(指令)優(yōu)化->高級(jí)程序里面的代碼級(jí)優(yōu)化(位運(yùn)算,前++和后++,數(shù)組和指針,if?else和switch?case等優(yōu)化)->算法優(yōu)化(流程優(yōu)化)->軟件架構(gòu)級(jí)優(yōu)化......
而現(xiàn)在我們討論的字節(jié)對(duì)齊屬于cpu級(jí)優(yōu)化,可以加速cpu讀取內(nèi)存時(shí)間。
什么是字節(jié)對(duì)齊?
現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對(duì)任何類型的變量的訪問(wèn)可以從任何地址開始,但實(shí)際情況是在訪問(wèn)特定類型變量的時(shí)候經(jīng)常在特定的內(nèi)存地址訪問(wèn),這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。?
為什么要字節(jié)對(duì)齊
原因一: CPU讀取內(nèi)存效率
各個(gè)硬件平臺(tái)對(duì)存儲(chǔ)空間的處理上有很大的不同。一些平臺(tái)對(duì)某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。
比如有些架構(gòu)的CPU在訪問(wèn)一個(gè)沒(méi)有進(jìn)行對(duì)齊的變量的時(shí)候會(huì)發(fā)生錯(cuò)誤(比如高通平臺(tái),一般的手機(jī)平臺(tái)都采用美國(guó)高通公司開發(fā)平臺(tái),對(duì)于無(wú)線上網(wǎng)卡來(lái)說(shuō),現(xiàn)在都是多核,要么是arm9+arm11,要么是arm9+2個(gè)Qdsp(Q是表示高通的dsp處理器)等處理器架構(gòu),然而在Qdsp中,如果訪問(wèn)了非對(duì)齊的內(nèi)存,就會(huì)直接發(fā)生錯(cuò)誤,直接把系統(tǒng)crush掉)那么在這種架構(gòu)下編程必須保證字節(jié)對(duì)齊。
其他平臺(tái)可能沒(méi)有這種情況,但是最常見(jiàn)的是如果不按照適合其平臺(tái)要求對(duì)數(shù)據(jù)存放進(jìn)行對(duì)齊,會(huì)在存取效率上帶來(lái)?yè)p失。比如有些平臺(tái)每次讀都是從偶地址開始,如果一個(gè)int型(假設(shè)為32位系統(tǒng))如果存放在偶地址開始的地方,那么一個(gè)讀周期就可以讀出這32bit。
而如果存放在奇地址開始的地方,就需要2個(gè)讀周期,并對(duì)兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該32bit數(shù)據(jù)。顯然在讀取效率上下降很多。?
原因二 : Cache親和性
結(jié)構(gòu)的數(shù)據(jù)跨越兩個(gè)cache line,就意味著兩次load或者兩次store。如果數(shù)據(jù)結(jié)構(gòu)是cache line對(duì)齊的,? 就有可能減少一次讀寫。
掌握字節(jié)對(duì)齊的估算方法
學(xué)會(huì)估算結(jié)構(gòu)大小,可以幫助我們更好地設(shè)計(jì)程序的數(shù)據(jù)結(jié)構(gòu),比如合理地節(jié)約內(nèi)存,提供數(shù)據(jù)訪問(wèn)效率等。
1. 對(duì)于基本數(shù)據(jù)類型對(duì)齊要求:
Char 類型一個(gè)字節(jié)對(duì)齊,可以從任何內(nèi)存地址讀取;
Short2個(gè)字節(jié),要求是從偶內(nèi)存地址讀取;
Int 4個(gè)字節(jié)(32位系統(tǒng)),要求是變量的內(nèi)存地址必須被4整除;
Long和int一樣(在32位系統(tǒng)中)
Double 8個(gè)字節(jié),要求是變量的內(nèi)存地址必須被8整除。
2. 對(duì)于struct和union對(duì)齊要求是:
在c語(yǔ)言中存在struct和union結(jié)構(gòu)體類型,屬于復(fù)雜類型,成員中自身對(duì)齊值最大的那個(gè)值?。
注:結(jié)構(gòu)的總大小為結(jié)構(gòu)的字節(jié)邊界數(shù)(即該結(jié)構(gòu)中占用最大空間的變量的類型所占用的字節(jié)數(shù))的倍數(shù), 對(duì)于結(jié)構(gòu)體最終大小,還要參考,指定對(duì)齊值n。
以下部分屬于具體計(jì)算方法(有很多公式,但這些公式你可能從來(lái)沒(méi)有看到過(guò)),不想看可以跳過(guò)這一節(jié)。
struct
s表示結(jié)構(gòu)體,假使結(jié)構(gòu)體有m個(gè)成員,?定義A(x)表示第x個(gè)成員的對(duì)齊值, X(i)表示其第i個(gè)成員(按順序從上往下)所占的大小,?H(x)表示前面x個(gè)成員最終占內(nèi)存大小, 則結(jié)構(gòu)體的大小可以通過(guò)下面公式估算出來(lái):
初始值--基本類型type對(duì)齊值:
??A(char)?=?1;
??A(short)?=?2;
??A(int)?=?4;
??A(long)?=?4;
??A(float)?=?4;
??A(double)?=?8;
基本類型成員對(duì)齊值,type為基本類型成員x對(duì)應(yīng)的基本類型:
第x個(gè)成員對(duì)齊值:
A(x) = min(A(type),n );????公式1
整個(gè)結(jié)構(gòu)對(duì)齊值:
A(s)?= min(max(A(1),A(2),...,A(m)),n)????公式2
則struct結(jié)構(gòu)體前x+1個(gè)成員和前x成員之間計(jì)算公式:
If(H(x)%A(x+1)?==?0)
??H(x+1)=?H(x)+X(x+1);
Else
??H(x+1)=?H(x)+A(x+1)-H(x)%A(x+1)+X(x+1);?
其中?H(1)?= X(1),A(1)=?X(1);???公式3
則結(jié)構(gòu)體最終結(jié)構(gòu)體大小X(s)為:
If(H(m)%A(s)?==?0)
???X(s) =?H(m);
Else
???X(s) =?H(m)+A(s)-H(m)%A(s);??公式4?
union
U表示這個(gè)結(jié)構(gòu)體,X(i)表示其第i個(gè)成員(按順序從上往下)所占的大小;假使結(jié)構(gòu)體有m個(gè)成員;結(jié)構(gòu)體的最終對(duì)齊值??
A(u)?=?min(max(A(1),A(2),...,A(m)),n)?公式5
定義H(x)表示前面x個(gè)成員實(shí)際占內(nèi)存大小;A(x)表示第x個(gè)成員的對(duì)齊值,可以有公式1給出,?Union結(jié)構(gòu)體最終占內(nèi)存大小為X(u),?則:
則union結(jié)構(gòu)體前x+1個(gè)成員和前x成員之間計(jì)算公式:
H(x+1)=?max(X(x+1),?H(x));
其中?H(1)?=?X(1);???公式6
則最終union結(jié)構(gòu)體大小X(u)為:
If(H(m)%A(u)?==?0)
???X(u) =?H(m);
Else
???X(u)?=?H(m)+A(u)-H(m)%A(u);??公式7?
公式4和公式7一般實(shí)現(xiàn):
指定對(duì)齊值
VC/VS編譯器
如果我們想指定對(duì)齊值,可以在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項(xiàng)卡Category的Code Generation選項(xiàng)的Struct Member Alignment中修改,默認(rèn)是8字節(jié),針對(duì)全部變量,如果想動(dòng)態(tài)改變部分,在vc中可以用宏命令?
#pragma pack (n)時(shí)的指定對(duì)齊值n
#pragma pack()取消,之間的數(shù)據(jù)都是指定為n,但不一定為對(duì)齊n。
最終的數(shù)據(jù)成員對(duì)齊值為:?自身對(duì)齊值和指定對(duì)齊值中小的那個(gè)值。
GCC編譯器
__attribute__((aligned(n)))?表示所定義的變量為n字節(jié)對(duì)齊。
__attribute__((__packed__))? 表示結(jié)構(gòu)體內(nèi)不填充多余的字節(jié),一般用于通信協(xié)議結(jié)構(gòu)體定義;
__attribute__((__aligned__(SMP_CACHE_BYTES)))表示結(jié)構(gòu)按cache對(duì)齊,提高數(shù)據(jù)訪問(wèn)效率。
結(jié)構(gòu)中帶有結(jié)構(gòu)
不必考慮整個(gè)子結(jié)構(gòu),只考慮子結(jié)構(gòu)的基本類型并參照前面的規(guī)則來(lái)分配空間。空結(jié)構(gòu)(即不帶任何的方法和數(shù)據(jù))占用的1字節(jié)的空間。
枚舉中(enum)?
枚舉始終占用4字節(jié)的空間。
結(jié)構(gòu)中成員
結(jié)構(gòu)的靜態(tài)成員不對(duì)結(jié)構(gòu)的大小產(chǎn)生影響,因?yàn)殪o態(tài)變量的存儲(chǔ)位置與結(jié)構(gòu)的實(shí)例地址無(wú)關(guān),要理解上面的對(duì)齊規(guī)則,最好是分析一些典型的對(duì)齊例子:
例子1:
struct?MyStruct?{?
??char?dda;?
??double?dda1;?
??int?type?
};?
默認(rèn)指定對(duì)齊值n = 8(vc),其他自己查看,則n = 8;
由公式1?得到此結(jié)構(gòu)體的最終對(duì)齊值為?A(s)?=?8
有上面公式2?,3?可以得到?X(MyStruct)(=sizeof(MyStruct))?:
H(1)?=?1,?H(2)?=(H(1))?1+7+8?=?16;?H(3)?=(H(2))?16+4?=?20;
X(s)?=?(H(3))20?+?(A(s)-H(3)%A(s))?4?=?24;
所以sizeof(MyStruct)?= 24;
例子2:
#pragma??pack(2)
struct?MyStruct?{?
??char?dda;???A(1)?=?1?
??double?dda1;?A(2)?=?2?
? int type ;? A(3)?= 2
};?
#pragma??pack()
由公式1,2得到此結(jié)構(gòu)體的最終對(duì)齊值為?A(s)?=?2
有上面公式3?,4?可以得到?X(MyStruct)(sizeof(MyStruct))?:
H(1)?= 1, H(2)?=(H(1)) 1+?(A(2)-H(1)%A(2)) 1+(X2)8 = 10;?因?yàn)镠(2)%A(3)==0;
所以?H(3)?=(H(2))?10+4?=?14;
因?yàn)镠(3)%A(s)?==?0;
所以X(s)?=?(H(3))?14;
所以sizeof(MyStruct)?= 14;
例子3:
這里有個(gè)結(jié)構(gòu)體嵌套例子,對(duì)于結(jié)構(gòu)體中的結(jié)構(gòu)體成員,不要認(rèn)為它的對(duì)齊方式就是他的大小,看下面的例子:
struct?s1{
char?a[8];?
};
struct?s2{
double?d;?
};
struct?s3{
s1?s;?
char?a;?
};
struct?s4{
s2?s;?
char?a;?
};
默認(rèn)指定對(duì)齊值n = 8;
A(s1)?= 1;A(s2)?=min(min(A(double), 8),8)?=8 ;?
A(s3)?=?min(max(A(x1)=min(A(s1)?=?1,8)?=?1,A(x2)?=?1),8)?=?1;
A(s4)?=?min(max(A(x1)=min(A(s2)?=?8,8)?=?8,A(x2)?=?1),8)?=?8;
X(s1)?=?8;
X(s2)?=?8;
X(s3)?=?9;
X(s4)?=?16;
以上只是一些測(cè)試?yán)?#xff0c;真實(shí)的結(jié)構(gòu)體都比較龐大,一般用sizeof就可以了,但心里要清楚,每個(gè)成員的偏移量和填充的字節(jié),這些都可以由上面的公式推出來(lái)(平時(shí)最應(yīng)該注意的),我這里就暫時(shí)不推導(dǎo)了。
字節(jié)對(duì)齊利弊
1. 對(duì)齊意味著可能有內(nèi)存浪費(fèi)(特別是數(shù)組這樣連續(xù)分配的數(shù)據(jù)結(jié)構(gòu)),所以需要在空間和時(shí)間兩方面權(quán)衡。
2. 跨平臺(tái)通信場(chǎng)景,每個(gè)平臺(tái)程序字節(jié)對(duì)齊不一樣,可能會(huì)導(dǎo)致內(nèi)存空間解析出錯(cuò),所以一般采用1字節(jié)對(duì)齊,中間不含填充數(shù)據(jù),但這樣會(huì)導(dǎo)致一些屬性訪問(wèn)性能下降,需要綜合考慮。
字節(jié)對(duì)齊總結(jié)
結(jié)構(gòu)體成員合理安排位置,節(jié)省空間,提高性能
跨平臺(tái)數(shù)據(jù)結(jié)構(gòu)可考慮1字節(jié)對(duì)齊,節(jié)省空間,解析安全,影響訪問(wèn)效率
跨平臺(tái)數(shù)據(jù)結(jié)構(gòu)進(jìn)行結(jié)構(gòu)優(yōu)化(對(duì)齊填充),提高訪問(wèn)效率,解析風(fēng)險(xiǎn),不節(jié)省空間
本地?cái)?shù)據(jù)采用chace對(duì)齊,提高訪問(wèn)效率
32位與64位默認(rèn)對(duì)齊數(shù)不一樣
五 最后總結(jié)
?
對(duì)于字節(jié)的理解,其實(shí)這些還不夠,掌握這些只是作為頂級(jí)c程序員最基本的要求(路還很長(zhǎng)),細(xì)節(jié)需要參考一下cpu的手冊(cè)。
其實(shí)在實(shí)際編程當(dāng)中,出現(xiàn)字節(jié)對(duì)齊的原因是通信的要求,如果是通過(guò)tcp/ip(互聯(lián)網(wǎng))通信,這樣一般協(xié)議頭部都是一個(gè)字節(jié)對(duì)齊,這樣對(duì)方解釋的時(shí)候是只需按協(xié)議解析就正確了;
或者是動(dòng)態(tài)庫(kù)調(diào)用,給別人的接口函數(shù)對(duì)應(yīng)參數(shù),如果是沒(méi)有滿足字節(jié)對(duì)齊的要求(不相同),如果進(jìn)行強(qiáng)制類型轉(zhuǎn)換或者按偏移量訪問(wèn)變量,就有可能出現(xiàn)錯(cuò)誤(某些嵌入式cpu)或者意想不到問(wèn)題,所以對(duì)于嵌入式開發(fā)的程序員最好是心里有數(shù)。
??
最后,讓我們來(lái)設(shè)計(jì)一個(gè)memcpy函數(shù),為什么要設(shè)計(jì)這個(gè)函數(shù)呢,如果你看過(guò)很多大型工程代碼,你就明白了,這個(gè)函數(shù)使用率相當(dāng)高,strcpy這個(gè)的優(yōu)化版本內(nèi)部都是調(diào)用memcpy來(lái)完成,這是系統(tǒng)函數(shù),每個(gè)平臺(tái)自己都實(shí)現(xiàn)了這個(gè)函數(shù),而且里面充滿很多編程技巧,看看自己會(huì)長(zhǎng)見(jiàn)識(shí)。
參考:
高通芯片平臺(tái)的memcpy函數(shù):
技術(shù):?內(nèi)存對(duì)齊? ? 循環(huán)展開 (局部性原理)? 批量copy
glibc庫(kù)函數(shù):
技術(shù):?內(nèi)存對(duì)齊? 批量copy ?
DPDK庫(kù)函數(shù):
技術(shù):?指令預(yù)取? 局部性原理? ?向量指令? cache對(duì)齊
注:?本文是我早期發(fā)表在CSDN上的文章,比較粗糙,現(xiàn)用公眾號(hào)記錄一下,希望和大家一起努力,朝頂級(jí)C程序員前進(jìn)
推薦閱讀
C++內(nèi)存管理全景指南
C++的最后一道坎|百萬(wàn)年薪的程序員
Linux調(diào)度系統(tǒng)全景指南(終結(jié)篇-調(diào)度優(yōu)化)
總結(jié)
- 上一篇: 硬核分析|腾讯云原生OS内存回收导致关键
- 下一篇: 顶级极客技术挑战赛,你敢来挑战吗?| 大