Linux GCC 64位編程技巧
linux GCC 64位編程技巧
64位係統的優勢?
既然要采用64位係統,首先要知道64位係統的優勢所在。對於技術人員來說,完全沒有必要去看那些廠家拿出的厚厚的說明書、或者某個研究機構拋出的一堆的數字,64位係統的優勢總結起來很簡單:內存大、速度快!
內存大
與32位係統相比,64位係統的地址空間大大增大,達到了18PB,18PB究竟是多大呢?說出來有點嚇人:4G內存的40億倍!這麼大的空間,不要說內存了,就是整個磁盤的數據都放進去也是沒有任何問題的。
需要注意的是:已有的32位係統由於采用了物理地址擴展技術(PAE,https://en.wikipedia.org/wiki/Physical_Address_Extension ),使得操作係統可用物理內存能夠超過4G,但對於單個程序來說,能夠使用的內存(即地址空間)還是隻有4G,這個是無法突破的。
速度快
你可能很容易就推斷如下結論:64位係統速度應該比32位快,而且應該是快兩倍!
但實際情況可能讓你有點吃驚:64位和速度沒有絕對的比例關係,甚至可能速度更低。
64位架構處理器就本身而言,不會在速度上兩倍於32位的同等處理器。為什麼呢?簡單來說:所謂的64位,隻是和內存地址空間有關,和CPU速度沒有一點關係,更不是64/32=2倍的關係。
另外,如果程序不需要對大量的數據進行處理,64位反而可能變慢,因為64位的地址是8位,32位的地址是4位,如果處理器的緩存是一樣大小,那麼很明顯64位處理器能夠緩存的東西是32位的一半,這反而降低了緩存命中率,因此影響了性能。
既然這樣,那我們為什麼還要說64位會更快呢?
我們知道,決定性能的不單是CPU,還有內存、磁盤、網絡等一大堆東東,而64位機器在性能上改善最大的就是內存容量大大增加,從4G擴展到18PB,這就意味著內存中可以放很多數據,避免頻繁的磁盤讀寫IO,從而大大提高性能;同時也意味著同一時間可以處理更多的數據,這也能夠大大提高性能,尤其是多核多CPU並行處理的時候。
CPU本身結構的變化也會帶來性能上的提升,這個提升主要體現在處理超過32位的整形數據上。對於32位係統,為了處理超過32位的整形數字,需要4次額外的寄存器操作;而64位係統,不需要這4次額外的寄存器操作。
某些CPU可能會為64位運算提供一些特定的特性,這也會帶來一些特定處理上的性能提升,不過這種提升和具體的CPU相關,不是通用的特性。相關信息可以參考:更快、更強 64位編程的三十二條軍規。
什麼情況下一定要使用64位?
64位雖好,但並不是放之四海皆準,畢竟從已有的32位升級到64位是要銀子的,如果你寫個“Hello, world!”也一定要求放到64位係統上來提高性能,那完全是浪費!
如下情況是必須用到64位係統的情況:
1. 程序(不是整個係統)需要超過4G的內存地址空間;
2. 使用libkvm庫,或者/dev/mem,/dev/kmem的文件;
3. 使用/proc調試64位進程;
4. 使用隻有64位版本的庫;
5. 需要64位寄存器來更高效的進行64位運算,例如處理long long類型數據;
6. 使用超過2G的文件;
32位和64位開發環境差別?
如果你是使用Java/Python等跨平台的語言進行開發,那麼恭喜你,所謂的64位和32位對你來說沒有差別,因為底層的虛擬機已經屏蔽掉了這種差異,在語言的層麵是不需要理解這種差異的(這也是跨平台的一個原因吧)。
但如果你是用C/C++,那就有點鬱悶了:C/C++是和係統強相關的,你在32位機器上寫的代碼,拿到64位機器上運行,可能出現你意料之外的結果,甚至可能崩潰,而且你還很難定位!
問題雖然很嚴重,而原因很簡單:32位係統使用的數據模型是ILP32,而64位係統使用的數據模型是LP64或者LLP64.
ILP32:指的是int, long, pointer長度是32位,取首字母合起來就是ILP32(下麵的簡寫都是這樣的),windows和Unix類32位係統都是這種模型;
LP64:指的是long, pointer是64位,這個是Unix類64位係統采用的數據模型;
LLP64:指的是long long, pointer是64位,而long還是32位,這是Windows的64位係統采用的模型;
除了這個最主要的區別外,另外一個區別就是有的庫可能隻有64位版本,但不會隻有32位版本,因為64位是支持所有32位的庫的。
Linux GCC 64位編程技巧
1 長整形數據
既然64位和32位開發環境主要的差別是數據模型的不同,那麼最簡單的一個方法就是盡力避開這種差異:咱們不用long類型了,管你32位還是64位,惹不起還躲不起麼?
如果一定要用長整形,也還是不要用long,直接用__int64_t,如果你覺得寫起來麻煩,那就自己定義為LLONG:typedef __int64_t LLONG即可
2 指針數據
long類型可以不用,但指針沒有辦法不用,那就隻能勇敢的麵對了:)
1.指針打印:在使用printf的時候,指針打印控製符是%p,不要用%d或者%i.
2.指針轉換:將指針轉換為整形數據的時候,使用intptr_t,不要使用int。
如果用C++的stream流,則不存在這些問題,直接輸出即可。
3 使用sizeof來確定長度
不管任何時候,都使用sizeof來確定變量或者類型的長度。
例如:對於結構來說,64位係統默認對齊的長度是8字節,而32位默認是4字節,因此在使用結構長度的時候,不能將長度寫死,而要使用sizeof計算。
4 注意常量類型陷阱
當你寫下long j = 1 << 32;的時候,你是否以為係統會將1識別為long類型,然後給j賦值為2的32次方?
然而係統卻在這裏設下了一個陷阱:如果你不指定常量類型的話,常量默認就是int類型的!
因此不管你是32位還是64位,j的值都是0。
要想避開這個陷阱,就要指定常量的類型(常量也是有類型的),因此要寫成:long j = 1L << 32;
常見常量類型:l/L,ll/LL,ul/UL,ull/ULL
5 注意sign extension陷阱
當負整數轉換為更長類型的無符號整數時,首先是轉換為目標類型對應的有符號類型,然後再轉換為無符號類型。
例如:
int i = -1;
unsigned long j = i;
則-1首先轉換為signed long型,再轉換為unsigned long型.
在這個轉換過程中,有一個小小的陷阱:當從短的數據類型轉到長的數據類型的時候,為了保證負數的有效性,會在多於的比特位填充1(因為負數的二進製碼是補碼,隻有補1才能保證負數的符號和取值都不會變).
例如:
int i = 0x80000000; //對應十進製的有符號數-2147483648或者無符號數2147483648
unsigned long j = i;
你可能以為j會直接等於0x0000000080000000,但事實上j會等於0xffffffff800000000;
6 size_t,pid_t等***_t類型
C/C++的庫為了支持跨32位和64位,很多函數返回值都是***_t,例如sizeof操作,這種***_t實際上就是隨32位或64位而變化的整形數。
1. 如果要使用返回值為***_t的函數,則變量也直接聲明為***_t;
2. 如果要打印***_t的變量,gcc可以使用%Zu, %Zd;或者直接將變量轉換為更長類型的數據後再打印,例如long或者long long,使用%lu, %llu, %ld, %lld控製符進行控製。
7 如果你不清楚長度,那麼就轉換為一個最長的數據
如果有的類型,如果你根本不知道它到底有多長,或者到底是什麼類型,那麼最簡單的方式就是將其轉換為一個最長的數據:整形當然是long long了,如果你連是否有符號都不知道,那就轉換為double吧。
8 打開所有編譯開關,關注警告
無論在32位還是64位係統下編譯,-Wall選項都打開,雖然這不能保證100%能夠發現所有問題,哪怕隻能解決一個,也能夠幫助你大大減少自己定位問題的時間。
參考資料
64位編程軍規:
sign extension:
https://en.wikipedia.org/wiki/Sign_extension
RHEL5相關數據類型以及長度一覽表:
最後更新:2017-04-02 06:51:27