歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Linux64位程序移植

Linux64位程序移植

日期:2017/3/1 9:21:18   编辑:Linux編程

1 概述

Linux下的程序大多充當服務器的角色,在這種情況下,隨著負載量和功能的增加,服務器所使用內存必然也隨之增加,然而32位系統固有的4GB虛擬地址空間限制,在如今已是非常突出的問題了;另一個需要改進的地方是日期,在Linux中,日期是使用32位整數來表示的,該值所表示的是從1970年1月1日至今所經過的秒數,這在2038年就會失效,但是在64位系統中,日期是使用64位整數表示的,基本上不用擔心其會失效。在這種情況下,將服務器移植到64位系統下,幾乎成了必然的選擇。要獲得能在64位系統下運行的程序,特別是達到只維護同一套代碼就能獲得在32位及64位系統下都能運行的程序,編碼時需遵循一定的原則,是一個較為繁瑣的過程。雖然有一些高級語言不會受這些數據類別變化的影響,但是C/C++的確會受到影響。下面,我們先來了解一下64位數據模型,為後面的介紹打下鋪墊。

2 64位系統數據模型

2.1 LP64/ILP64/LLP64

下面的表格說明了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數據類型的寬度從一種模型變換成另外一種模型時,應用程序可能會受到很多方面的影響。這些影響主要可以分為兩類:

  • 數據對象的大小。編譯器按照自然邊界對數據類型進行對齊;換而言之,32位的數據類型在64位系統上要按照32位邊界進行對齊,而64位的數據類型在64位系統上則要按照64位邊界進行對齊。這意味著諸如結構或聯合之類的數據對象的大小在32位和64位系統上是不同的。
  • 基本數據類型的大小。通常關於基本數據類型之間關系的假設在64位數據模型上都已經無效了。依賴於這些關系的應用程序在64位平台上編譯也會失敗。例如,sizeof (int) = sizeof (long) = sizeof (pointer) 的假設對於ILP32數據模型有效,但是對於其他數據模型就無效了。

總之,編譯器要按照自然邊界對數據類型進行對齊,這意味著編譯器會進行“填充”,從而強制進行這種方式的對齊,就像是在C結構和聯合中所做的一樣。結構或聯合的成員是根據最寬的成員進行對齊的。Windows 64位系統采用LLP64的數據模型,從Win32到Win64就只有指針長度不同,因此移植較為簡單。而Linux 64位系統采用LP64數據模型,因此在long和pointer上,都有著和32位系統不同的長度。

2.2 數據對齊

默認情況下,編譯器按照自然邊界對數據類型進行對齊;換而言之,32位的數據類型在64位系統上要按照32位邊界進行對齊,而64位的數據類型在64位系統上則要按照64位邊界進行對齊。

2.2.1 #pragma pack

上面談到,默認情況下,編譯器按照自然邊界對數據類型進行對齊,但使用編譯器指令#pragma pack可以修改對齊方式。

2.2.2 結構體對齊舉例

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位的對象進行處理。

3 從32位系統移植到64位系統

3.1 基本原則

3.1.1 類型定義

不要使用C/C++中那些在64位系統上會改變大小的數據類型來編寫應用程序,而是使用一些類型定義或宏來顯式地說明變量中所包含的數據的大小和類型。有些定義可以使代碼的可移植性更好。

l ptrdiff_t:

這個值在32位系統下是int,在64位系統下是long,表示兩個指針相減後的結果。

l size_t:

這個值在32位系統下是unsigned int,在64位系統下是unsigned long,用來表示非負的大小,一般用來表示sizeof的結果或表示數組的大小。

  • int32_t、uint32_t 等:

定義具有預定義寬度的整型。

  • intptr_t 和 uintptr_t:

這2個值在32位系統下是int和unsigned int,在64位系統下是long和unsigned long,任何有效指針都可以轉換成這個類型。

3.1.2 表達式

在C/C++中,表達式是基於結合律、操作符的優先級和一組數學計算規則的。要想讓表達式在32位和64位系統上都可以正確工作,請注意以下規則:

  • 兩個有符號整數相加的結果是一個有符號整數。
  • int和long類型的兩個數相加,結果是一個long類型的數。
  • 如果一個操作數是無符號整數,另外一個操作數是有符號整數,那麼表達式的結果就是無符號整數。
  • int和double類型的兩個數相加,結果是一個double類型的數。此處int類型的數在執行加法運算之前轉換成double類型。
3.1.3 賦值
  • sizeof和數組大小:

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)

{

……

}

這樣有可能導致數據截斷。

  • time_t:

不要使用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);

3.2 移植經驗

3.2.1 如何判斷一個可執行文件是32位編譯的版本還是64位編譯的版本
  • 使用file可執行文件名

顯示ELF 64-bit LSB executable 則是64位可執行文件版本

顯示ELF 32-bit LSB 則是32位可執行文件版本

  • 使用readelf -h可執行文件名,看其中的Class

顯示ELF64是64位可執行文件

顯示ELF32是32位可執行文件

3.2.2 如何判斷環境是32位還是64位

代碼中:

#if __WORDSIZE == 64

#endif

腳本中:

if [ `getconf LONG_BIT` -eq 64 ];then

64位處理邏輯

else

32位處理邏輯

fi

3.2.3 數據定義

修改所有long定義的變量為int類型,由於long類型在32位和64位下的長度是不一樣的,為了避免兼容性問題,盡量檢查和修改掉類型定義為非固定長度的整數類型。

指針類型的,如果做加減等運算處理,不能轉換為int類型,而統一改為intptr_t類型,比如:

intptr_toffset = (intptr_t)pCurr – (intptr_t)pBase;

3.2.4 格式化字符串的時候

#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等來作一些文章。

3.2.5 基本數據定義

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;

3.2.6 time_t的加減要注意

比如下面這段代碼,在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);

}

3.3 移植步驟

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位程序重新生成,供程序使用

4 總結

主流的硬件供應商最近都在擴充自己的64位產品,這是因為64位平台可以提供更好的性能和可伸縮性。32位系統的限制,特別是4GB的虛擬內存上限,已經極大地刺激很多公司開始考慮遷移到64位平台上。了解如何將應用程序移植到64位體系結構上可以幫助我們編寫可移植性更好且效率更高的代碼。

Copyright © Linux教程網 All Rights Reserved