Linux下的程序大多充當服務器的角色,在這種情況下,隨著負載量和功能的增加,服務器所使用內存必然也隨之增加,然而32位系統固有的4GB虛擬地址空間限制,在如今已是非常突出的問題了;另一個需要改進的地方是日期,在Linux中,日期是使用32位整數來表示的,該值所表示的是從1970年1月1日至今所經過的秒數,這在2038年就會失效,但是在64位系統中,日期是使用64位整數表示的,基本上不用擔心其會失效。在這種情況下,將服務器移植到64位系統下,幾乎成了必然的選擇。要獲得能在64位系統下運行的程序,特別是達到只維護同一套代碼就能獲得在32位及64位系統下都能運行的程序,編碼時需遵循一定的原則,是一個較為繁瑣的過程。雖然有一些高級語言不會受這些數據類別變化的影響,但是C/C++的確會受到影響。下面,我們先來了解一下64位數據模型,為後面的介紹打下鋪墊。
下面的表格說明了32位和64位數據模型在各個數據類別上的區別,這裡的I是指int,L是指long,P是指pointer:
Datatype
LP64
ILP64
LLP64
ILP32
LP32
char
8
8
8
8
8
short
16
16
16
16
16
int
32
64
32
32
16
long
64
64
32
32
32
long long
64
64
64
64
64
pointer
64
64
64
32
32
表2.1
這3個64位模型(LP64、LLP64和ILP64)之間的區別在於非浮點數據類型。當一個或多個C數據類型的寬度從一種模型變換成另外一種模型時,應用程序可能會受到很多方面的影響。這些影響主要可以分為兩類:
總之,編譯器要按照自然邊界對數據類型進行對齊,這意味著編譯器會進行“填充”,從而強制進行這種方式的對齊,就像是在C結構和聯合中所做的一樣。結構或聯合的成員是根據最寬的成員進行對齊的。Windows 64位系統采用LLP64的數據模型,從Win32到Win64就只有指針長度不同,因此移植較為簡單。而Linux 64位系統采用LP64數據模型,因此在long和pointer上,都有著和32位系統不同的長度。
默認情況下,編譯器按照自然邊界對數據類型進行對齊;換而言之,32位的數據類型在64位系統上要按照32位邊界進行對齊,而64位的數據類型在64位系統上則要按照64位邊界進行對齊。
上面談到,默認情況下,編譯器按照自然邊界對數據類型進行對齊,但使用編譯器指令#pragma pack可以修改對齊方式。
struct test
{
int i1;
double d;
int i2;
long l;
}
結構成員
在 32 位系統上的大小
在 64 位系統上的大小
struct test {
int i1;
32位
32位
32位填充
double d;
64位
64位
int i2;
32位
32位
32位填充
long l;
32位
64位
};
結構大小為20字節
結構大小為32字節
表2.2
注意,在我自己所測試的32位系統上,編譯器並沒有對double型數據進行對齊,盡管它是一個64位的對象,這是因為硬件會將其當成兩個32位的對象進行處理。
不要使用C/C++中那些在64位系統上會改變大小的數據類型來編寫應用程序,而是使用一些類型定義或宏來顯式地說明變量中所包含的數據的大小和類型。有些定義可以使代碼的可移植性更好。
l ptrdiff_t:
這個值在32位系統下是int,在64位系統下是long,表示兩個指針相減後的結果。
l size_t:
這個值在32位系統下是unsigned int,在64位系統下是unsigned long,用來表示非負的大小,一般用來表示sizeof的結果或表示數組的大小。
定義具有預定義寬度的整型。
這2個值在32位系統下是int和unsigned int,在64位系統下是long和unsigned long,任何有效指針都可以轉換成這個類型。
在C/C++中,表達式是基於結合律、操作符的優先級和一組數學計算規則的。要想讓表達式在32位和64位系統上都可以正確工作,請注意以下規則:
vector<int> intArray;
……
int arraysz = (int)intArray.size();
不要int類型來接收STL數據類型的大小,而應該使用size_t:
size_t arraysz = intArray.size();
上面這種是比較明顯的錯誤,不明顯的錯誤有:
for (int i = 0; i < intArray.size(); ++i)
{
……
}
這樣有可能導致數據截斷。
不要使用int類型參與時間的運算,因為time_t是long類型,在64位機器上會導致數據截斷,原則是與時間相關的運算都采用time_t類型。
例如在32位程序中可能有如下代碼:
long m_lastHeartBeatTime; //最後心跳時間
int GetLastHeartBeatTime()
{
return m_lastHeartBeatTime;
}
time_t currtime = GetCurrentTime();
if(currtime >= GetLastHeartBeatTime())
{
SetLastHeartBeatTime(currtime);
}
這些代碼在32位系統下沒有問題,但在64位系統下可能會導致嚴重的問題。
vector<int> intArray;
……
size_t arraysz = intArray.size();
32位系統下代碼應為:
printf(“array size = %u”, arraysz);
64位系統下代碼應為:
printf(“array size = %lu”, arraysz);
顯示ELF 64-bit LSB executable 則是64位可執行文件版本
顯示ELF 32-bit LSB 則是32位可執行文件版本
顯示ELF64是64位可執行文件
顯示ELF32是32位可執行文件
代碼中:
#if __WORDSIZE == 64
#endif
腳本中:
if [ `getconf LONG_BIT` -eq 64 ];then
64位處理邏輯
else
32位處理邏輯
fi
修改所有long定義的變量為int類型,由於long類型在32位和64位下的長度是不一樣的,為了避免兼容性問題,盡量檢查和修改掉類型定義為非固定長度的整數類型。
指針類型的,如果做加減等運算處理,不能轉換為int類型,而統一改為intptr_t類型,比如:
intptr_toffset = (intptr_t)pCurr – (intptr_t)pBase;
#if __WORDSIZE == 64
#define FMT_SIZET "%u"
#define FMT_UINT64 "%llu"
#define FMT_INT64 "%lld"
#else
#define FMT_SIZET "%lu"
#define FMT_UINT64 "%lu"
#define FMT_INT64 "%lld"
#endif
例如:
sprintf(errorDesc,"Insufficient memory buffer size,"FMT_SIZET" needed,but only "FMT_SIZET" bytes",unit_size,m_capacity);
當然也可以使用系統定義的宏PRIu64和PRId64等來作一些文章。
long, time_t, size_t 類型在32位和64位下的長度是不一樣的,要檢查代碼中是否有time_t *,size_t *類型的指針參數,由於調用傳入的變量大部分是int類型,所以將這些函數定義統一修改為int*,同時仔細檢查所有調用的地方,傳入的指針變量長度是否匹配。
比如下面的范例:
int Func1(size_t *pSize1,size_t size2); 需要修改為
int Func1(int *pSize1,size_t size2); 其中size2是非指針類型,可以不需要修改。
然後檢查調用的地方,如果傳入參數是非int類型,則需要修改為int類型變量傳入,比如
short shParam = 0;
Func1(&shParam,100);
要修改為
int iParam = 0;
Func1(&iParam,100);
如果是一些已經定義好的結構體成員,則可通過臨時變量來修改
Func(&stPlayer.shParam,100)
修改為
int iTmpParam = stPlayer.shParam;
Func(&iTmpParam,100);
stPlayer.shParam = iTmpParam;
比如下面這段代碼,在32位系統上運行沒有問題,但64位下運行異常:
if((leftTime + xxz::framework::GetCurrentTimeVal(NULL)) > 0 && (leftTime >= 0))
{
n->expireTime = leftTime + xxz::framework::GetCurrentTimeVal(NULL);
}
else
{
n->expireTime = 0x7FFFFFFF;
}
這裡在64位下,如果lefttime等於0x7FFFFFFF,則lefttime + xxz::framework::GetCurrentTimeVal(NULL)的結果為long(因為xxz::framework::GetCurrentTimeVal(NULL)返回time_t,為long類型),因此不會溢出,這時相加的結果賦給一個整形(n->expireTime),則這個整形溢出,稱為負值,從而發生錯誤。
改為:
if ((int64)leftTime + (int64)xxz::framework::GetCurrentTimeVal(NULL) >= (int64)0x7FFFFFFF)
{
dstTime = 0x7FFFFFFF;
}
else
{
dstTime = leftTime + xxz::framework::GetCurrentTimeVal(NULL);
}
1 修改代碼,主要注意以下事項
去除所有的long,替換為固定大小的類型,如int32_t, int64_t等。
時間相關類型的全部用使用time_t來進行處理。
pointer之間的加減法使用intptr_t來存儲結果,不要在pointer和int之間相互轉換。
如果必須用size_t,比如STL,則傳值賦值都用size_t,不要在int和size_t之間相互轉換,以免結果被截斷。
格式化字符串使用如下的兼容性定義來處理,避免告警:
#if __WORDSIZE == 64
#define FMT_SIZET "%u"
#else
#define FMT_SIZET "%lu"
2 替換外部庫
這一步比較難,因為有些外部庫沒有64位版本,這就有可能需要推動外部庫的64位化工作,或者將這部分功能挪到其它進程。
3 運營環境
修改腳本支持64位環境
一些數據需要用64位程序重新生成,供程序使用
主流的硬件供應商最近都在擴充自己的64位產品,這是因為64位平台可以提供更好的性能和可伸縮性。32位系統的限制,特別是4GB的虛擬內存上限,已經極大地刺激很多公司開始考慮遷移到64位平台上。了解如何將應用程序移植到64位體系結構上可以幫助我們編寫可移植性更好且效率更高的代碼。