做32位/64位跨版本編程,有什麼需要注意的么?

32位/64位對編程人員的區別,除了指針跟long int長度不一樣外,還有什麼要注意的地方嘛?


先搞清楚編譯器的行為:

1. 64位有LLP64,LP64,ILP64幾種處理方式,先弄清楚具體是哪種。

2. 有些64位編譯器仍然有常數默認int的行為,別以為到了64位就變了。

再具體到代碼里:

1. 注意64位數據(指針)在傳參時的行為,有些編譯器允許不聲明函數直接用,但後果可能是參數被截斷。

2. 數據類型盡量用int32_t,uint32_t,int64_t,uint64_t這些,並且要密切注意數據溢出(被截斷)的風險,不要直接用int,long這些。

3. 同一種數據,在各個驅動、模塊、子系統內定義要一致,比如表示塊設備塊號、扇區號的變數是64位,但某些存儲介質(SD/USB)驅動里可能寫成的是32位的,那麼這就要改;但如果是IPv4地址,就要明確定義成uint32_t,如果不小心用了ulong_t那麼就得檢查一下了。

4. 移位操作一定要小心,32位數據和64位數據的移位在CPU上行為並不一樣。

5. 確定結構體的大小和填充部分,64位情況下結構體如果帶指針成員,那麼對齊會發生變化,如果用一些非標準行為去訪問,就會有問題。

6. 訪問底層硬體時注意數據類型轉換,CPU是64位的,但很多設備可訪問的內存仍然是32位的,會影響DMA的操作,甚至需要重寫DMA庫。


多用 int##_t、uint##_t、intptr_t、size_t


@北極 大大的回答很贊。裡面提到的一點我也想說說。

4. 移位操作一定要小心,32位數據和64位數據的移位在CPU上行為並不一樣。

這讓我想起我們寫編譯器的時候遇到過的另一種情況。

例如說,下面這條x86指令:

movl %edi, %edi

在x86-32上,這就是個nop,如果想消除nop的話可以把這條指令消除掉。

但在x86-64上,這條指令的語義有:

32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register.

所以它有副作用——把 %rdi 的高32位清零,所以並不是一個nop,所以不能隨便消除掉。

有時候我們的編譯器里的寄存器分配器在插入register shuffling code的時候會插入像這樣的指令,在x86-32上我們會直接檢查到src和dst相同而不插入該指令,但在x86-64上我們就得仔細檢查上下文來決定到底是否可以不插入該指令…

相似的,我們會嘗試用x86的 lea 指令來計算某些加法/乘法。對於32位int,在x86-32上我們可以直接用 leal 來做這樣的加法/乘法,像是:

leal 0x100(%edi, %ecx, 0x2), %eax

這樣會計算 eax = edi + ecx * 2 + 0x100。而在x86-64上,同樣對於32位int,我們得確保在地址計算中出現的寄存器事先對高32位有合適的清零或者帶符號擴展,然後再用 leal 做計算。我在實際項目代碼里已經被前人這樣坑過好幾次了…


調用 C 語言的 variadic function,傳 NULL 參數要特別小心。因為 0 只會 promote 到 int,不會 promote 到 long,因此需要 cast。

例如 execl("/bin/ls", "ls", "-l", NULL); 在 32-bit 下是正確的,但是在 64-bit 下可能是錯的。

最好寫成 execl("/bin/ls", "ls", "-l", (char*)NULL);


現在的答案我都不滿意,都談的是技巧,沒有討論問題的實質。問題的實質是,你要基於C語言的語義來編程。所有平台,以及編譯器,首先是要滿足C語言的語義,一個int,就是一個整數,至少有16位,如此而已,你不能作其他假設,無論它的endian,對齊還是它的最大字長。這就叫基於語義編程,而不是基於實現編程。

如果你需要一個指針,就定義指針,你需要一個固定字長的內存,就用指定的字長,這兩者不能輕易對等。編到有類似需求的地方再找方案(有很多),不清楚的地方要對齊標準,以此為基礎決定如何針對平台實現進行優化,否則記一堆的原則,你還是抓不住要點。

至於那些彙編啊,驅動啊的問題,都和這個問題無關。彙編是編譯器的問題,不是寫程序的問題。驅動的硬體有長度要求,有對齊要求,那是硬體對程序的要求,不是程序怎麼寫的問題。ioctl_compat是linux內核實現規矩的要求,也不是寫程序的問題。


說一個冷一點的。

有些函數以及浮點運算,要求有align 方式,典型RISC cpu的系統,對align要求比較嚴格。

align按照32位地址對齊或者按照64位地址對齊,如果沒有對齊,處理器可能陷入某種exception,從而影響處理效率。。


要搞清楚x86_64並不代表所有的64位cpu。其它的64位表現可能是不同的。


有時候需要額外注意內存對齊。

比如msvc,64位下malloc是16位元組對齊的,但32位下就不是了。

之前寫SSE代碼的時候x64下沒問題。一切到32 位,崩了。

還有個不太切題的:注意不同編譯器有不同行為,不要依賴那些沒有統一的行為。雖然可能32/64位都不會有問題,但誰知道不會換編譯器呢。


參數傳遞區別很大,32是用棧,64是先用寄存器。雖然這是彙編的內容,但c總免不了要跟彙編接觸。還有浮點,32位是float計算快,64位應該用double。float會轉換成double,計算完成再轉回float,反而慢。


除非寫彙編,否則不要對目標機器的字長做任何假定,需要固定寬度的變數應使用int*_t。


其實這就是如何寫一個移植性好的代碼。我一直想研究下這個。


其實常用的就一點,盡量不要使用long,在32和64下字長是不一樣的,另外指針是和long一樣的字長。


補充一點,對於鏈接庫一定要區分32位和64位。


推薦閱讀:

學好C/C++,Linux。可以從事什麼工作?
斷網焦慮症?
玩 Arduino 需要做哪些準備?
Arduino、arm、樹莓派、單片機四者有什麼不同?
對Arduino設為輸出(OUTPUT)的引腳進行寫操作(digitalWrite)的作用是什麼?

TAG:編程 | Linux | C編程語言 | 嵌入式系統 | C |