Linux GCC 64位程式設計技巧

工程師WWW發表於2013-12-07

64位系統的優勢?

既然要採用64位系統,首先要知道64位系統的優勢所在。對於技術人員來說,完全沒有必要去看那些廠家拿出的厚厚的說明書、或者某個研究機構丟擲的一堆的數字,64位系統的優勢總結起來很簡單:記憶體大、速度快

記憶體大

32位系統相比,64位系統的地址空間大大增大,達到了18PB18PB究竟是多大呢?說出來有點嚇人:4G記憶體的40億倍!這麼大的空間,不要說記憶體了,就是整個磁碟的資料都放進去也是沒有任何問題的。

需要注意的是:已有的32位系統由於採用了實體地址擴充套件技術(PAEhttp://en.wikipedia.org/wiki/Physical_Address_Extension ),使得作業系統可用實體記憶體能夠超過4G,但對於單個程式來說,能夠使用的記憶體(即地址空間)還是隻有4G,這個是無法突破的。

速度快

你可能很容易就推斷如下結論:64位系統速度應該比32位快,而且應該是快兩倍!

但實際情況可能讓你有點吃驚:64位和速度沒有絕對的比例關係,甚至可能速度更低。

64位架構處理器就本身而言,不會在速度上兩倍於32位的同等處理器。為什麼呢?簡單來說:所謂的64位,只是和記憶體地址空間有關,和CPU速度沒有一點關係,更不是64/322倍的關係。

另外,如果程式不需要對大量的資料進行處理,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(下面的簡寫都是這樣的)windowsUnix32位系統都是這種模型;

LP64:指的是long, pointer64位,這個是Unix64位系統採用的資料模型;

LLP64:指的是long long, pointer64位,而long還是32位,這是Windows64位系統採用的模型;

 

除了這個最主要的區別外,另外一個區別就是有的庫可能只有64位版本,但不會只有32位版本,因為64位是支援所有32位的庫的。

Linux GCC 64位程式設計技巧

1  長整形資料

既然64位和32位開發環境主要的差別是資料模型的不同,那麼最簡單的一個方法就是盡力避開這種差異:我們們不用long型別了,管你32位還是64位,惹不起還躲不起麼?

如果一定要用長整形,也還是不要用long,直接用__int64_t,如果你覺得寫起來麻煩,那就自己定義為LLONGtypedef  __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賦值為232次方?

然而系統卻在這裡設下了一個陷阱:如果你不指定常量型別的話,常量預設就是int型別的

因此不管你是32位還是64位,j的值都是0

 

要想避開這個陷阱,就要指定常量的型別(常量也是有型別的),因此要寫成:long j = 1L << 32;

常見常量型別:l/Lll/LLul/ULull/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_tpid_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位程式設計軍規:

http://www.vipcn.com/chengxukaifa/shujujiegou/gengkuaigengqiang-64weibianchengdesanshiertiaojungui.html

 

sign extension

http://en.wikipedia.org/wiki/Sign_extension

 

RHEL5相關資料型別以及長度一覽表:

相關文章