32位程序和64位程序這些區(qū)別你知道嗎?
本文轉(zhuǎn)載自微信公眾號「 編程珠璣」,轉(zhuǎn)載本文請聯(lián)系 編程珠璣公眾號。
作者:守望,linux應(yīng)用開發(fā)者,目前在公眾號【編程珠璣】 分享Linux/C/C++/數(shù)據(jù)結(jié)構(gòu)與算法/工具等原創(chuàng)技術(shù)文章和學(xué)習(xí)資源。
我們在編寫C/C++程序時,32位程序和64位程序的代碼有何區(qū)別?如何編寫既可以編譯成32位程序又可以編譯成64位程序的代碼?
代碼上的區(qū)別
實(shí)際上,對于32位程序和64位程序來說,代碼上的區(qū)別不大,嚴(yán)格來說,甚至是一樣的,它的主要區(qū)別在于一些基本數(shù)據(jù)類型占用的字節(jié)長度不一樣(注:這里僅針對類Unix平臺)
類型 | 32位占用字節(jié) | 64位占用字節(jié) |
---|---|---|
long | 4 | 8 |
unsigned long | 4 | 8 |
指針 | 4 | 8 |
當(dāng)然這里的long包括一些用它定義的類型,如time_t,它的長度也是有區(qū)別的,關(guān)于time_t,還有一個有意思的問題《什么是2038問題》。
除此之外,其默認(rèn)對齊字節(jié)數(shù),也不一樣,32位程序?yàn)?字節(jié),64位程序默認(rèn)為8字節(jié)。關(guān)于字節(jié)對齊,可參考《理一理字節(jié)對齊的那些事》。
可執(zhí)行文件上的區(qū)別
來看個小例子吧,看看他們有何區(qū)別。
- //來源:公眾號編程珠璣
- //作者:守望先生
- test.c
- #include<stdio.h>
- struct Test
- {
- int a;
- long b;
- };
- int main(void)
- {
- printf("sizeof(long) = %zu\n",sizeof(long)); //long類型占用字節(jié)數(shù)
- printf("sizeof(unsigned long) = %zu\n",sizeof(unsigned long));//unsigned long類型占用字節(jié)數(shù)
- struct Test test = {1,2};
- printf("sizeof(struct Test) = %zu\n",sizeof(test));//用于測試對齊字節(jié)數(shù)
- printf("sizeof(pointer) = %zu\n",sizeof(&test)); //指針占用字節(jié)數(shù)
- return 0;
- }
如果你的系統(tǒng)是64位,默認(rèn)編譯為64位程序,而如果需要編譯為32位程序,則需要帶上-m32參數(shù),如果你的系統(tǒng)是32位的,那么是不能直接運(yùn)行64位程序的,但是如果是64位的,是可以運(yùn)行32位程序的。(實(shí)際上你在下載軟件的時候需要選擇位數(shù)的時候,就需要注意了,如果你的系統(tǒng)32位的, 但是你下載了一個64位的程序包,自然是不可用的,但是反過來卻可以。)
編譯為32位程序運(yùn)行:
- $ gcc -o test32 test.c -m32
- $ ./test32
- sizeof(long) = 4
- sizeof(unsigned long) = 4
- sizeof(struct Test) = 8
- sizeof(pointer) = 4
編譯位64位程序運(yùn)行:
- $ gcc -o test64 test.c
- $ ./test64
- sizeof(long) = 8
- sizeof(unsigned long) = 8
- sizeof(struct Test) = 16
- sizeof(pointer) = 8
通過運(yùn)行結(jié)果,我們也可以看出前面提到的差別。
那么可執(zhí)行文件本身有什么差別呢?
- $ readelf -h test32
- ELF Header:
- Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
- Class: ELF32
- Data: 2's complement, little endian
- Version: 1 (current)
- OS/ABI: UNIX - System V
- ABI Version: 0
- Type: EXEC (Executable file)
- Machine: Intel 80386
- (...)
可以看到Class屬性標(biāo)識為ELF32。
而對于64位:
- readelf -h test64
- ELF Header:
- Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
- Class: ELF64
- Data: 2's complement, little endian
- Version: 1 (current)
- OS/ABI: UNIX - System V
- ABI Version: 0
- Type: EXEC (Executable file)
- Machine: Advanced Micro Devices X86-64
它的屬性為ELF64。實(shí)際上我們可以通過readelf發(fā)現(xiàn)很多信息。
例如你在進(jìn)行交叉編譯后出現(xiàn)鏈接錯誤或者最后的執(zhí)行程序在目標(biāo)機(jī)器無法運(yùn)行,則可以查看Machine部分,看看程序是否能在你想要的平臺運(yùn)行。
例如64位程序中的Machine中顯示的是Advanced Micro Devices X86-64,至少說明它在arm平臺是沒法正常運(yùn)行的。
一個程序最大能申請多少內(nèi)存空間?
還記得這道面試題嗎?如果你只是回答Linux理論最大不超過3G,windows不超過G,那肯定是不完整的,這里必須要區(qū)分32位程序和64位程序。
這一點(diǎn)在《解引用NULL為什么會掛死?》中已經(jīng)有所提及。32位決定了其虛擬地址空間的最大值為2^32,即4G,除去操作系統(tǒng)占用的1G左右,剩下3G左右,當(dāng)然了這里面3G包含了所有代碼,數(shù)據(jù)等,總結(jié)就是,最終能使用的不超過3G。不到3G的地址空間。(注這里并不表示它只能訪問計(jì)算機(jī)4G的內(nèi)存,而是表示最大尋址范圍為4G)。那么64位的虛擬地址空間擴(kuò)展到了17179869184G,所以,看出差別了嗎?
通過上面簡單的分析可以發(fā)現(xiàn),64位程序理論能使用的內(nèi)存是驚人的,而32位程序卻非常有限,除此之外,還有一個在《什么是2038問題》》中提到的問題,就是2038年后,32位程序?qū)⒑茈y正常使用時間相關(guān)的處理。
當(dāng)然了,64位系統(tǒng)通常能夠支持更高精度的浮點(diǎn)運(yùn)算。
同時支持32位和64位代碼編寫原則
基于前面提到的原因,很多傳統(tǒng)系統(tǒng)都開始著手移植到64位系統(tǒng)上,而如果原先代碼就非常規(guī)范的話,移植工作還算比較輕松,鏈接64位庫,編譯成64位程序即可,但是如果沒有遵循以下原則,那么工作量就比較大了:
依賴long類型和指針類型占用空間大小以及其表示范圍
當(dāng)然,對于這個原則,其表現(xiàn)可能非常多。
long和int混用
例如:
- void test(long len)
- {
- int localLen = len;
- xxxx;
- }
這里很明顯可能會發(fā)現(xiàn)截斷。最常見的就是:
- int len = sizeof(xxx);
當(dāng)然,這里大多數(shù)情況下也不會有太大問題,直到其長度大于int表示范圍。
慎用掩碼定義
我們可能經(jīng)常需要定義一些掩碼:
- long mask = OxFFFFFFFFL;
在 32 位系統(tǒng)上,這會將所有位都置位(每位全為 1),但是在 64 位系統(tǒng)上,只有低 32 位被置位了。結(jié)果是這個值是 0x00000000FFFFFFFF。
如果希望所有位置1,那么可以:
- long mask = 1L << ((sizeof(long) * 8) - 1);
打印指針
32下,這樣的沒問題的:
- int a = 10;
- int *p = &a;
- printf("%x",p);
但是64位下,打印不完全。自然要使用:
- printf("%p",p);
傳送結(jié)構(gòu)體數(shù)據(jù)
在32位和64位系統(tǒng)中,其默認(rèn)對齊字節(jié)數(shù)是不一樣的。
- strcut test
- {
- int a;
- long b;
- }
如果對方是64位,發(fā)送過來上述結(jié)構(gòu)體數(shù)據(jù),而你的是32位程序,可想而知,結(jié)果并不會如你所愿。前面占用空間16字節(jié),而后者占用空間8字節(jié)。
顯示定義long
如果你的數(shù)據(jù)類型是long,那么可以使用L顯示說明:
- long i = 1 << a;
上面的寫法建議換成:
- long i = 1L << a;
避免數(shù)據(jù)被截斷。
總結(jié)
關(guān)于這樣的點(diǎn)還有很多,這里不一一介紹。本文簡單介紹了32位程序和64位程序的區(qū)別,以及移植過程中需要注意的原則。實(shí)際上編寫同時能夠運(yùn)行在32位和64位系統(tǒng)上的整體原則基本如下:
- 不要試圖假定數(shù)據(jù)類型的占用空間
- 顯示區(qū)別使用int和long
而前面提到的一些問題,其實(shí)通過一些代碼檢查工具就很容易發(fā)現(xiàn)了,不放過小的警告,基本能解決大部分問題。