閱讀943 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Linux GCC 64位編程技巧

                                 linux GCC 64位編程技巧

64位係統的優勢?

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

內存大

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

需要注意的是:已有的32位係統由於采用了物理地址擴展技術(PAEhttps://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位編程軍規:

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

 

sign extension

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

 

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

最後更新:2017-04-02 06:51:27

  上一篇:go magento -- 如何訂閱後台帶密碼的RSS
  下一篇:go 關於java.lang.NoClassDefFoundError: com/sun/mail/util/LineInputStream解決辦法