linux GCC 64位編程技巧

           linux GCC 64位編程技巧

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位編程技巧

 長整形數據

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

如果一定要用長整形,也還是不要用long,直接用__int64_t,如果你覺得寫起來麻煩,那就自己定義爲LLONGtypedef  __int64_t LLONG即可

 指針數據

long類型可以不用,但指針沒有辦法不用,那就只能勇敢的面對了:)

1.指針打印:在使用printf的時候,指針打印控制符是%p,不要用%d或者%i.

2.指針轉換:將指針轉換爲整形數據的時候,使用intptr_t,不要使用int

 

如果用C++stream流,則不存在這些問題,直接輸出即可。

 使用sizeof來確定長度

不管任何時候,都使用sizeof來確定變量或者類型的長度。

例如:對於結構來說,64位系統默認對齊的長度是8字節,而32位默認是4字節,因此在使用結構長度的時候,不能將長度寫死,而要使用sizeof計算。

 注意常量類型陷阱

當你寫下long  j = 1 << 32;的時候,你是否以爲系統會將1識別爲long類型,然後給j賦值爲232次方?

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

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

 

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

常見常量類型:l/Lll/LLul/ULull/ULL

 注意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;

 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控制符進行控制。

 如果你不清楚長度,那麼就轉換爲一個最長的數據

如果有的類型,如果你根本不知道它到底有多長,或者到底是什麼類型,那麼最簡單的方式就是將其轉換爲一個最長的數據:整形當然是long long了,如果你連是否有符號都不知道,那就轉換爲double吧。

 打開所有編譯開關,關注警告

無論在32位還是64位系統下編譯,-Wall選項都打開,雖然這不能保證100%能夠發現所有問題,哪怕只能解決一個,也能夠幫助你大大減少自己定位問題的時間。

參考資料

64位編程軍規:

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

 

sign extension

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

 

RHEL5相關數據類型以及長度一覽表:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章