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

32

32

32

long long

64

64

64

64

64

pointer

64

64

32

32

表2.1

这3个64位模型(LP64、LLP64和ILP64)之间的区别在于非浮点数据类型。当一个或多个C数据类型的宽度从一种模型变换成另外一种模型时,应用程序可能会受到很多方面的影响。这些影响主要可以分为两类:

l  数据对象的大小。编译器按照自然边界对数据类型进行对齐;换而言之,32位的数据类型在64位系统上要按照32位边界进行对齐,而64位的数据类型在64位系统上则要按照64位边界进行对齐。这意味着诸如结构或联合之类的数据对象的大小在32位和64位系统上是不同的。

l  基本数据类型的大小。通常关于基本数据类型之间关系的假设在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的结果或表示数组的大小。

l  int32_t、uint32_t 等:

定义具有预定义宽度的整型。

l  intptr_t 和 uintptr_t:

这2个值在32位系统下是int和unsigned int,在64位系统下是long和unsigned long,任何有效指针都可以转换成这个类型。

3.1.2表达式

在C/C++中,表达式是基于结合律、操作符的优先级和一组数学计算规则的。要想让表达式在32位和64位系统上都可以正确工作,请注意以下规则:

l  两个有符号整数相加的结果是一个有符号整数。

l  int和long类型的两个数相加,结果是一个long类型的数。

l  如果一个操作数是无符号整数,另外一个操作数是有符号整数,那么表达式的结果就是无符号整数。

l  int和double类型的两个数相加,结果是一个double类型的数。此处int类型的数在执行加法运算之前转换成double类型。

3.1.3 赋值

l  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)

{

……

}

这样有可能导致数据截断。

l  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位系统下可能会导致严重的问题。

l  格式化打印

vector<int> intArray;

……

size_t arraysz = intArray.size();

32位系统下代码应为:

printf(“array size = %u”, arraysz);

64位系统下代码应为:

printf(“array size = %lu”, arraysz);

3.2 移植经验

位编译的版本还是64位编译的版本

l  使用file可执行文件名

显示ELF 64-bit LSB executable 则是64位可执行文件版本

显示ELF 32-bit LSB 则是32位可执行文件版本

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

显示ELF64是64位可执行文件

显示ELF32是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位体系结构上可以帮助我们编写可移植性更好且效率更高的代码。

Linux64位程序移植的更多相关文章

  1. Linux64位程序中的漏洞利用

    之前在栈溢出漏洞的利用和缓解中介绍了栈溢出漏洞和一些常见的漏洞缓解 技术的原理和绕过方法, 不过当时主要针对32位程序(ELF32). 秉承着能用就不改的态度, IPv4还依然是互联网的主导, 更何况 ...

  2. [百度空间] [转]将程序移植到64位Windows

    from : http://goooder.bokee.com/2000373.html (雷立辉 整理) 简介:本文对如何将32位Windows程序平滑的支持和过渡到64位Windows操作系统做出 ...

  3. 【转】将 Linux 应用程序移植到 64 位系统上

    原文网址:http://www.ibm.com/developerworks/cn/linux/l-port64.html 随着 64 位体系结构的普及,针对 64 位系统准备好您的 Linux® 软 ...

  4. STM32F407使用MFRC522射频卡调试及程序移植成功

    版权声明:转载请注明出处,谢谢 https://blog.csdn.net/Kevin_8_Lee/article/details/88865556 或  https://www.cnblogs.co ...

  5. 012_STM32程序移植之_内部flash开机次数管理lib库建立

    012_STM32程序移植之_内部flash开机次数管理lib库建立 1. 测试环境:STM32C8T6 2. 测试接口: 3. 串口使用串口一,波特率9600 单片机引脚------------CH ...

  6. STM32F429 LCD程序移植

    STM32F429自带LCD驱动器,这一具有功能给我等纠结于屏幕驱动的程序员带来了很大的福音.有经验的读者一定有过这样的经历,用FSMC驱动带由控制器的屏幕时候,一旦驱动芯片更换,则需要重新针对此驱动 ...

  7. 嵌入式linux应用程序移植方法总结

    嵌入式linux应用程序移植方法总结 前段时间一直在做openCapwap的移植和调试工作,现在工作已接近尾声,编写本文档对前段工作进行一个总结,分享下openCapwap移植过程中的经验和感悟.江浩 ...

  8. linux第三方程序移植

    摘要:在linux开发过程中经常需要用到第三方的程序,有时需要用到它们的库,有时需要它们生成的可执行文件,如何正确地编译这些第三方的程序,以方便地使用和开发自己需要的程序,将是本文要论述的内容. 1. ...

  9. 64位系统上运行32位程序能否申请到8G内存?

    申请不到,因为64为系统在运行32位程序的时候只是为了向下兼容而已,对于32位程序来讲,申请8G的存储空间没有任何意义,因为32位的程序最大寻址空间只有4G,32位程序在编译之后的机器代码也只有32位 ...

随机推荐

  1. webstorm编辑器设置为vim的方法

    首先有这个插件,其设置如下,选中即可 打开和关闭方法: https://www.jetbrains.com/help/webstorm/vim-emulation.html https://plugi ...

  2. C语言中不同类型的数据转换规则

    不同类型数据间的混合运算与类型转换 1.自动类型转换 在C语言中,自动类型转换遵循以下规则: ①若参与运算量的类型不同,则先转换成同一类型,然后进行运算 ②转换按数据长度增加的方向进行,以保证精度不降 ...

  3. 在vs2012中配置使用iisexpress

    在vs2012中配置使用iisexpress   vs2012支持基于iisexpress的web站点调试,这样可以尽可能与生产环境具备一样的环境. 但是,如果在vs2012中直接配置iis目录,通常 ...

  4. git学习资料及心得

    1. 阮一峰的(简单易懂,实用性佳) http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html http://www.ruanyifeng ...

  5. Convert WebP to PNG using java

    WebP是谷歌的图片格式,java 类库imageio 是不支持此种格式的.眼下除了在线转换以及工具以外,第三方类库转换webp格式大致有: 1.linux:Google libwebp 既是类库也能 ...

  6. phpcms前台任意代码执行漏洞(php<5.3)

    phpcms v9 中 string2array()函数使用了eval函数,在多个地方可能造成代码执行漏洞 /phpsso_server/phpcms/libs/functions/global.fu ...

  7. MySQL 系列教程(二) 你不知道的数据库操作

    本章内容: 查看\创建\使用\删除 数据库 用户管理及授权实战 局域网远程连接法 查看\创建\使用\删除\清空\修改 数据库表(是否可空,默认值,主键,自增,外键) 表内容的增删改查 where条件. ...

  8. jquery插件:aotocomplete

    aotocomplete.js http://blog.csdn.net/smeyou/article/details/7980273?_t_t_t=0.3565731019350138 $(func ...

  9. Linux常见服务器——DHCP服务器的搭建

    一.基础知识: 1.DHCP简介: DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)通常被应用在大型的局域网络环境中,主要作用是集中的管理.分配IP ...

  10. jQuery表单 Ajax向PHP服务端发送文件请求并返回数据

    ImageAjaxUpLoad.htm <!DOCTYPE html> <head> <meta charset='utf-8'> <title>< ...