转载】将32位代码向64位平台移植的注意事项
轉(zhuǎn)載】將32位代碼向64位平臺移植的注意事項
新近的64位平臺在二進制上與32位應(yīng)用程序兼容,這意味著可以非常簡單地移植現(xiàn)有的程序。許多目前在32位平臺上運行良好的程序也許不必移植,除非程序有以下要求:
·需要多于4GB的內(nèi)存。
·使用的文件大小常大于2GB。
·密集浮點運算,需要利用64位架構(gòu)的優(yōu)勢。
·能從64位平臺的優(yōu)化數(shù)學(xué)庫中受益。
否則,只需簡單地重新編譯一下,就已經(jīng)足夠了。大多數(shù)編寫良好的程序不費吹灰之力就可移植到64位平臺之上,在此假定你的程序編寫良好,并熟悉本文將要討論的問題。?
ILP32和LP64數(shù)據(jù)模型
32位環(huán)境涉及"ILP32"數(shù)據(jù)模型,是因為C數(shù)據(jù)類型為32位的int、long、指針。而64位環(huán)境使用不同的數(shù)據(jù)模型,此時的long和指針已為64位,故稱作"LP64"數(shù)據(jù)模型。
現(xiàn)今所有64位的類Unix平臺均使用LP64數(shù)據(jù)模型,而64位Windows使用LLP64數(shù)據(jù)模型,除了指針是64位,其他基本類型都沒有變。我們在此主要探討ILP32到LP64的移植問題,表1顯示了ILP32與LP64數(shù)據(jù)模型的差異。
向64位移植代碼時的所有問題差不多都可以總結(jié)出一個簡單的規(guī)律:千萬不要認為int、long、指針的長度一樣。任何違反這條規(guī)律的代碼,當(dāng)運行在 LP64數(shù)據(jù)模型下時,都會出現(xiàn)不同的問題,而且很難找出原因所在。例1中有許多違反這條規(guī)律的地方,其在移植到64位平臺上時都需要重寫。
例1:
| 1 int *myfunc(int i) 2 { 3 return(&i); 4 } 5 6 int main(void) 7 { 8 int myint; 9 long mylong; 10 int *myptr; 11 12 char *name = (char * ) getlogin(); 13 14 printf("Enter a number %s: ", name); 15 (void) scanf("%d", &mylong); 16 myint = mylong; 17 myptr = myfunc(mylong); 18 printf("mylong: %d pointer: %x \n", mylong, myptr); 19 myint = (int)mylong; 20 exit(0); 21 22 } |
第一步是要求編譯器捕捉到移植時的問題,因所用編譯器的不同,選項可能也有所不同,但對IBM XL編譯器系列,可用的選項有-qwarn64 -qinfo=pro,為了得到64位可執(zhí)行文件,可使用選項-q64(如果使用GCC,選項應(yīng)為-m64,表2中列出了其他可用的GCC選項)。圖1是編譯例1中代碼時的情況。
| 編譯例1中代碼時的情況 |
缺少原型的截斷
如果一個函數(shù)被調(diào)用時沒有指定函數(shù)原型,返回值將是32位的int。不使用原型的代碼可能會發(fā)生意料之外的數(shù)據(jù)截斷,由此導(dǎo)致一個分割錯誤。編譯器捕捉到了例1中第12行的這個錯誤。
char *name = (char *) getlogin();
編譯器假定函數(shù)返回一個int值,并截短結(jié)果指針。這行代碼在ILP32數(shù)據(jù)模型下工作正常,因為此時的int和指針是同樣長度,換到LP64模型中,就不一定正確了,甚至于類型轉(zhuǎn)換都不能避免這個錯誤,因為getlogin()在返回之后已經(jīng)被截斷了。
要修正這個問題,需包括頭文件<unistd.h>,其中有g(shù)etlogin()的函數(shù)原型。
格式指定符
如果對64位long、指針使用了32位格式指定符,將導(dǎo)致程序錯誤。編譯器捕捉到了例1中第15行的這個錯誤。
(void) scanf("%d", &mylong);
注意,scanf將向變量mylong中插入一個32位的值,而剩下的4字節(jié)就不管了。要修正這個問題,請在scanf中使用%ld指定符。
第18行也演示了在printf中的一個類似的問題:
printf("mylong: %d pointer: %x \n", mylong, myptr);
要修正此處的錯誤,mylong應(yīng)使用%ld,對myptr使用 %p而不是%x。
賦值截斷
有關(guān)編譯器發(fā)現(xiàn)賦值截斷的一個例子在第16行中:
myint = mylong;
這在ILP32模型下不會有任何問題,因為此時的int、long都是32位,而在LP64中,當(dāng)把mylong賦值給myint時,如果數(shù)值大于32位整數(shù)的最大值時,數(shù)值將被截短。
被截斷的參數(shù)
編譯器發(fā)現(xiàn)的下一個錯誤在第17行中,雖然myfunc函數(shù)只接受一個int參數(shù),但調(diào)用時卻用了一個long,參數(shù)在傳遞時會悄無聲息地被截斷。
轉(zhuǎn)換截斷
轉(zhuǎn)換截斷發(fā)生在把long轉(zhuǎn)換成int時,比如說例1中的第19行:
| myint = (int) mylong; |
導(dǎo)致轉(zhuǎn)換截斷的原因是int與long非同樣長度。這些類型的轉(zhuǎn)換通常在代碼中以如下形式出現(xiàn):
| int length = (int) strlen(str); |
strlen返回size_t(它在LP64中是unsigned long),當(dāng)賦值給一個int時,截斷是必然發(fā)生的。而通常,截斷只會在str的長度大于2GB時才會發(fā)生,這種情況在程序中一般不會出現(xiàn)。雖然如此,也應(yīng)該盡量使用適當(dāng)?shù)亩鄳B(tài)類型(如size_t、uintptr_t等等),而不要去管它最下面的基類型是什么。
一些其他的細小問題
編譯器可捕捉到移植方面的各種問題,但不能總指望編譯器為你找出一切錯誤。
那些以十六進制或二進制表示的常量,通常都是32位的。例如,無符號32位常量0xFFFFFFFF通常用來測試是否為-1:
| #define INVALID_POINTER_VALUE 0xFFFFFFFF |
然而,在64位系統(tǒng)中,這個值不是-1,而是4294967295;在64位系統(tǒng)中,-1正確的值應(yīng)為0xFFFFFFFFFFFFFFFF。要避免這個問題,在聲明常量時,使用const,并且?guī)蟬igned或unsigned。
| const signed int INVALID_POINTER_VALUE = 0xFFFFFFFF; |
這行代碼將會在32位和64位系統(tǒng)上都運行正常。
其他有關(guān)于對常量硬編碼的問題,都是基于對ILP32數(shù)據(jù)模型的不當(dāng)認識,如下:
| int **p; p = (int**)malloc(4 * NO_ELEMENTS); |
這行代碼假定指針的長度為4字節(jié),而這在LP64中是不正確的,此時是8字節(jié)。正確的方法應(yīng)使用sizeof():
| int **p; p = (int**)malloc( sizeof(*p) * NO_ELEMENTS); |
注意對sizeof()的不正確用法,例如:
| sizeof(int) = = sizeof(int *); |
這在LP64中是錯誤的。
符號擴展
要避免有符號數(shù)與無符號數(shù)的算術(shù)運算。在把int與long數(shù)值作對比時,此時產(chǎn)生的數(shù)據(jù)提升在LP64和ILP32中是有差異的。因為是符號位擴展,所以這個問題很難被發(fā)現(xiàn),只有保證兩端的操作數(shù)均為signed或均為unsigned,才能從根本上防止此問題的發(fā)生。
例2:
| long k; int i = -2; unsigned int j = 1; k = i + j; printf("Answer: %ld\n", k); |
你無法期望例2中的答案是-1,然而,當(dāng)你在LP64環(huán)境中編譯此程序時,答案會是4294967295。原因在于表達式(i+j)是一個 unsigned int表達式,但把它賦值給k時,符號位沒有被擴展。要解決這個問題,兩端的操作數(shù)只要均為signed或均為unsigned就可。像如下所示:
| k = i + (int) j |
聯(lián)合體問題(Union)
當(dāng)聯(lián)合本中混有不同長度的數(shù)據(jù)類型時,可能會導(dǎo)致問題。如例3是一個常見的開源代碼包,可在ILP32卻不可在LP64環(huán)境下運行。代碼假定長度為2的unsigned short數(shù)組,占用了與long同樣的空間,可這在LP64平臺上卻不正確。
例3:
?
| typedef struct { unsigned short bom; unsigned short cnt; union { unsigned long bytes; unsigned short len[2]; } size; } _ucheader_t; |
?
要在LP64上運行,代碼中的unsigned long應(yīng)改為unsigned int。要在所有代碼中仔細檢查聯(lián)合體,以確認所有的數(shù)據(jù)成員在LP64中都為同等長度。
字節(jié)序問題(Endian)
因64位平臺的差異,在移植32位程序時,可能會失敗,原因可歸咎于機器上字節(jié)序的不同。Intel、IBM PC等CISC芯片使用的是Little-endian,而Apple之類的RISC芯片使用的是Big-endian;小尾字節(jié)序(Little- endian)通常會隱藏移植過程中的截斷bug。
例4:
?
| long k; int *ptr; int main(void) { k = 2 ; ptr = &k; printf("k has the value %ld, value pointed to by ptr is %ld\n", k, *ptr); return 0; } |
?
例4是一個有此問題的明顯例子,一個聲明指向int的指針,卻不經(jīng)意間指向了long。在ILP32上,這段代碼打印出2,因為int與long長度一樣。但到了LP64上,因為int與long的長度不一,而導(dǎo)致指針被截斷。不管怎么說,在小尾字節(jié)序的系統(tǒng)中,代碼依舊會給出k的正確答案2,但在大尾字節(jié)序(Big-endian)系統(tǒng)中,k的值卻是0。
找出64位移植問題的可用的GCC鄙夷選項
表3說明了為什么在不同的字節(jié)序系統(tǒng)中,會因截斷問題而產(chǎn)生不同的答案。在小尾字節(jié)序中,被截斷的高位地址中全為0,所以答案仍為2;而在大尾字節(jié)序中,被截斷的高位地址中包含值2,這樣就導(dǎo)致結(jié)果為0,所以在兩種情況下,截斷都是一種 bug。但要意識到,小尾字節(jié)序會隱藏小數(shù)值的截斷錯誤,而這個錯誤只有在移植到大尾字節(jié)序系統(tǒng)上時才可能被發(fā)現(xiàn)。
移植到64位平臺之后的性能降低
當(dāng)代碼移植到64位平臺之后,也許發(fā)現(xiàn)性能實際上降低了。原因與在LP64中的指針長度和數(shù)據(jù)大小有關(guān),并由此引發(fā)的緩存命中率降低、數(shù)據(jù)結(jié)構(gòu)膨脹、數(shù)據(jù)對齊等問題。?
由于64位環(huán)境中指針?biāo)加玫淖止?jié)更大,致使原來運行良好的32位代碼出現(xiàn)不同程度的緩存問題,具體表現(xiàn)為執(zhí)行效率降低。可使用工具來分析緩存命中率的變化,以確認性能降低是否由此引起。
在遷移到LP64之后,數(shù)據(jù)結(jié)構(gòu)的大小可能會改變,此時程序可能會需要更多的內(nèi)存和磁盤空間。例如,圖2中的結(jié)構(gòu)在ILP32中只需要16字節(jié),但在 LP64中,卻需要32字節(jié),整整增長了100%。這緣于此時的long已是64位,編譯器為了對齊需要而加入了額外的填充數(shù)據(jù)。
通過改變結(jié)構(gòu)中數(shù)據(jù)排列的先后順序,能將此問題所帶來的影響降到最小,并能減少所需的存儲空間。如果把兩個32位int值放在一起,會因為少了填充數(shù)據(jù),存儲空間也隨之減少,現(xiàn)在存儲整個結(jié)構(gòu)只需要24字節(jié)。
在重排數(shù)據(jù)結(jié)構(gòu)之前,在根據(jù)數(shù)據(jù)使用的頻度仔細衡量,以免因降低緩存命中率而帶來性能上的損失。
如何生成64位代碼
在一些情況中,32位和64位程序在源代碼級別的接口上很難區(qū)分。不少頭文件中,都是通過一些測試宏來區(qū)分它們,不幸的是,這些特定的宏依賴于特定的平臺、特定的編譯器或特定的編譯器版本。舉例來說,GCC 3.4或之后的版本都定義了__LP64__,以便為所有的64位平臺通過選項-m64編譯產(chǎn)生64位代碼。然而,GCC 3.4之前的版本卻是特定于平臺和操作系統(tǒng)的。?
也許你的編譯器使用了不同于 __LP64__的宏,例如IBM XL的編譯器當(dāng)用-q64編譯程序時,使用了__64bit__宏,而另一些平臺使用_LP64,具體情況可用__WORDSIZE來測試一下。請查看相關(guān)編譯器文檔,以便找出最適合的宏。例5可適用于多種平臺和編譯器:
例5:
| #if defined (__LP64__) || defined (__64BIT__) || defined (_LP64) || (__WORDSIZE == 64) printf("I am LP64\n"); #else printf("I am ILP32 \n"); #endif |
共享數(shù)據(jù)
在移植到64位平臺時的一個典型問題是,如何在32位和64位程序之間讀取和共享數(shù)據(jù)。例如一個32位程序可能把結(jié)構(gòu)體作為二進制文件存儲在磁盤上,現(xiàn)在你要在64位代碼中讀取這些文件,很可能會因LP64環(huán)境中結(jié)構(gòu)大小的不同而導(dǎo)致問題。
對那些必須同時運行在32位和64位平臺上的新程序而言,建議不要使用可能會因LP64和ILP32而改變長度的數(shù)據(jù)類型(如long),如果實在要用,可使用頭文件<inttypes.h>中的定寬整數(shù),這樣不管是通過文件還是網(wǎng)絡(luò),都可在32位和64位的二進制層面共享數(shù)據(jù)。
例6:
| #include <stdio.h> #include <inttypes.h> struct on_disk { /* ILP32|LP64共享時,這個應(yīng)該使用int32_t */ long foo; }; int main() { FILE *file; struct on_disk data; #ifdef WRITE file=fopen("test","w"); data.foo = 65535; fwrite(&data, sizeof(struct on_disk), 1, file); #else file = fopen("test","r"); fread(&data, sizeof(struct on_disk), 1, file); printf("data: %ld\n", data.foo); #endif fclose(file); } |
來看一下例6,在理想的情況下,這個程序在32位和64位平臺上都可正常運行,并且可以讀取對方的數(shù)據(jù)。但實際上卻不行,因為long在ILP32和 LP64之中長度會變化。結(jié)構(gòu)on_disk里的變量foo應(yīng)該聲明為int32_t,這個定寬類型可保證在當(dāng)前ILP32或移植到的LP64數(shù)據(jù)模型下,都生成相同大小的數(shù)據(jù)。
混合Fortran和C的問題
許多科學(xué)運算程序從C/C++中調(diào)用 Fortran的功能,Fortran從它本身來說并不存在移植到64位平臺的問題,因為Fortran的數(shù)據(jù)類型有明確的比特大小。然而,如果混合 Fortran和C語言,問題就來了,如下:例7中C語言程序調(diào)用例8中Fortran語言的子例程。
例7:
| void FOO(long *l); main () { long l = 5000; FOO(&l); } |
例8:
| subroutine foo( i ) integer i write(*,*) 'In Fortran' write(*,*) i return end subroutine foo |
例9:
| % gcc -m64 -c cfoo.c % /opt/absoft/bin/f90 -m64 cfoo.o foo.f90 -o out % ./out In Fortran 0 |
當(dāng)鏈接這兩個文件后,程序?qū)⒋蛴〕鲎兞縤的值為"5000"。而在LP64中,程序打印出"0",因為在LP64模式下,子例程foo通過地址傳遞一個 64位的參數(shù),而實際上,Fortran子例程想要的是一個32位的參數(shù)。如果要改正這個錯誤,在聲明Fortran子例程變量i時,把它聲明為 INTEGER*8,此時和C語言中的long為一樣長度。
結(jié)論
64位平臺是解決大型復(fù)雜科學(xué)及商業(yè)問題的希望,大多數(shù)編寫良好的程序可輕松地移植到新平臺上,但要注意ILP32和LP64數(shù)據(jù)模型的差異,以保證有一個平滑的移植過程。
關(guān)注 - 8
粉絲 - 1 +加關(guān)注 0 0 (請您對文章做出評價) ? 上一篇:去掉指定列的unique key
? 下一篇:【轉(zhuǎn)載】解決在Vim中鼠標(biāo)右鍵不能粘貼
posted @ 2012-03-14 09:45 centimeter 閱讀(553) 評論(0) ?編輯 收藏 刷新評論刷新頁面返回頂部 注冊用戶登錄后才能發(fā)表評論,請 登錄 或 注冊,訪問網(wǎng)站首頁。 【免費課程】分享:PHP開發(fā)APP接口
【推薦】50萬行VC++源碼: 大型組態(tài)工控、電力仿真CAD與GIS源碼庫
融云,免費為你的App加入IM功能——讓你的App“聊”起來!! 最新IT新聞:
· 蘋果上季或銷售7300萬部iPhone 同比增長5.8%
· 微軟收購R語言開發(fā)公司 強化云計算業(yè)務(wù)
· 趣聞:程序猿產(chǎn)品汪市場雞注定不如運營喵?
· 微軟重金收購機器學(xué)習(xí)廠商Equivio為哪般?
· 20年前的虛擬現(xiàn)實技術(shù)竟然是這樣!
? 更多新聞... 最新知識庫文章:
· 淺析數(shù)據(jù)化設(shè)計思維
· 門戶級UGC系統(tǒng)的技術(shù)進化路線
· 億級用戶下的新浪微博平臺架構(gòu)
· 技術(shù)團隊的情緒與效率
· 關(guān)于請求被掛起頁面加載緩慢問題的追查
? 更多知識庫文章... 昵稱:centimeter
園齡:4年1個月
粉絲:1
關(guān)注:8 +加關(guān)注
| |||||||||
| 28 | 29 | 30 | 31 | 1 | 2 | 3 | |||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 | |||
| 11 | 12 | 13 | 14 | 15 | 16 | 17 | |||
| 18 | 19 | 20 | 21 | 22 | 23 | 24 | |||
| 25 | 26 | 27 | 28 | 29 | 30 | 31 | |||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | |||
搜索
?總結(jié)
以上是生活随笔為你收集整理的转载】将32位代码向64位平台移植的注意事项的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 画个火山图,标记下基因的名字
- 下一篇: Circulation:吃鸡蛋量多,死亡