Linux64位程序移植
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位程序移植的更多相关文章
- Linux64位程序中的漏洞利用
之前在栈溢出漏洞的利用和缓解中介绍了栈溢出漏洞和一些常见的漏洞缓解 技术的原理和绕过方法, 不过当时主要针对32位程序(ELF32). 秉承着能用就不改的态度, IPv4还依然是互联网的主导, 更何况 ...
- [百度空间] [转]将程序移植到64位Windows
from : http://goooder.bokee.com/2000373.html (雷立辉 整理) 简介:本文对如何将32位Windows程序平滑的支持和过渡到64位Windows操作系统做出 ...
- 【转】将 Linux 应用程序移植到 64 位系统上
原文网址:http://www.ibm.com/developerworks/cn/linux/l-port64.html 随着 64 位体系结构的普及,针对 64 位系统准备好您的 Linux® 软 ...
- STM32F407使用MFRC522射频卡调试及程序移植成功
版权声明:转载请注明出处,谢谢 https://blog.csdn.net/Kevin_8_Lee/article/details/88865556 或 https://www.cnblogs.co ...
- 012_STM32程序移植之_内部flash开机次数管理lib库建立
012_STM32程序移植之_内部flash开机次数管理lib库建立 1. 测试环境:STM32C8T6 2. 测试接口: 3. 串口使用串口一,波特率9600 单片机引脚------------CH ...
- STM32F429 LCD程序移植
STM32F429自带LCD驱动器,这一具有功能给我等纠结于屏幕驱动的程序员带来了很大的福音.有经验的读者一定有过这样的经历,用FSMC驱动带由控制器的屏幕时候,一旦驱动芯片更换,则需要重新针对此驱动 ...
- 嵌入式linux应用程序移植方法总结
嵌入式linux应用程序移植方法总结 前段时间一直在做openCapwap的移植和调试工作,现在工作已接近尾声,编写本文档对前段工作进行一个总结,分享下openCapwap移植过程中的经验和感悟.江浩 ...
- linux第三方程序移植
摘要:在linux开发过程中经常需要用到第三方的程序,有时需要用到它们的库,有时需要它们生成的可执行文件,如何正确地编译这些第三方的程序,以方便地使用和开发自己需要的程序,将是本文要论述的内容. 1. ...
- 64位系统上运行32位程序能否申请到8G内存?
申请不到,因为64为系统在运行32位程序的时候只是为了向下兼容而已,对于32位程序来讲,申请8G的存储空间没有任何意义,因为32位的程序最大寻址空间只有4G,32位程序在编译之后的机器代码也只有32位 ...
随机推荐
- How to create an IPA (Xcode 5)
This tutorial will walk you through the easiest way to generate an IPA using Xcode 5. We will be usi ...
- DotNetBrowser入门教程(更新完善中)
DotNetBrowser 希望实现的目标:桌面软件可以完美运行Html5,内置支持MVC与WebSocket的微型服务器. 基于.Net 4.0开发.开发环境:VS2017,运行环境支持Window ...
- Windows下cwRsyncServer双机连续同步部署
下载cwRsyncServer服务器端与客户端的安装文件:服务端下载:cwRsyncServer_4.0.5_Installer.zip客户端下载:cwRsync_4.0.5_Installer.zi ...
- Vue表单和组件
一.表单 v-model 指令在表单控件元素上创建双向数据绑定,v-model 会根据控件类型自动选取正确的方法来更新元素. <input v-model="message" ...
- ambari journalnode异常Can't scan a pre-transactional edit log
今天在删日志文件,不知道删错哪个地方了. 该目录下一直报错,这个日志文件增长很快, /var/log/hadoop/hdfs/ hadoop-hdfs-journalnode-xx.log 先备份/h ...
- 【共享单车】—— React后台管理系统开发手记:权限设置和菜单调整(未完)
前言:以下内容基于React全家桶+AntD实战课程的学习实践过程记录.最终成果github地址:https://github.com/66Web/react-antd-manager,欢迎star. ...
- 比特币Bitcoin-qt客户端加密前后如何导入导出私钥?
一.Bitcoin-qt客户端加密后 如需要导出某一地址对应的私钥,需要先调用 walletpassphrase 密码 解锁持续时间(秒), 如:walletpassphrase h123456789 ...
- Elasticsearch教程(八) elasticsearch delete 删除数据(Java)
Elasticsearch的删除也是很灵活的,下次我再介绍,DeleteByQuery的方式.今天就先介绍一个根据ID删除.上代码. package com.sojson.core.elasticse ...
- [Other] An Overview of Arrays and Memory
One integer takes 32bit in memory, 1 byte = 8bits, therefore one integer takes 4 bytes. Now let's as ...
- 最小化JavaScript代码
1.去除不必要的格式符.空白符.凝视符. 这个操作.事实上能够理解为是一种格式化.尽管它操作的结果事实上是去除掉原始文件的那些格式. 2.模糊(Obfuscation)处理JAVASCRIP脚本源码. ...