简单改变之完美跨越32位与64位平台

本文介绍如何编写既适用于32位又适用于64位平台的C++代码,通过采用合适的类型和最佳实践,确保代码在未来能够顺利过渡到64位环境。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简单改变之完美跨越32位与64位平台
 
         并不能因为现在不会将代码移植到64位,就可以忽略以下新的编码准则。现在,只需一些简单的调整,就可让你的C++代码完美跨越32/64位平台,以便在将来64位需要来临时,让你处于有利的位置。
 
         在90年代初,64位系统还被认为是一个“等待问题的解决方案”;而到了2005年,64位技术已快速地聚集了大量的用户,即使你的开发暂时只针对32位平台,但在代码中加入与64位的兼容性将有助于未来向64位的平滑过渡。本文讲述的就是怎样编写跨越32位及64位平台的代码。
 
 
         当处于64位时
         64位架构提供了以下优势:
²          支持更大的文件:大文件是指包含2GB或更多数据的文件。
² 64位寻址:64位处理器具有16 EB(千兆兆字节)的虚拟寻址空间,这相当于目前4GB寻址上限的40亿倍。
² 打破了4GB内存的界限:在64位系统上,理论上最大支持物理内存的上限为16 EB。
 
作者注:64位环境中使用的内存单位:
l 1 TB = 1024 GB
l 1 PB = 1024 TB = 1048576 GB
l 1 EB = 1024 PB = 1048576 TB = 1,073,741,824 GB
 
为充分利用上述有利条件,必须使用64位编译器及64位库分别重新编译及链接源代码。好消息是,目前的32位可执行文件依然可运行于64位模式下,反之不可,64位二进制代码只能运行于64位模式中。
 
 
         ILP32 vs LP64
         编写与64位系统兼容的程序的主要问题,在于32位与64位系统的不同数据类型模型。32位数据类型模型被称为ILP32,也就是说int、long、指针都具有32位的同等大小;64位数据类型模型被称为LP64,在此之下,int依然为32位,然而,long与指针却是64位。下表概述了两种模型的不同之处,并列出了每种基本类型下的所占位数:
 
C/C++本地类型
ILP32
LP64
char
8
8
bool
通常与char相同
通常与char相同
short
16
16
int
32
32
long
32
64
long long
64
64
(数据或函数的)指针
32
64
enum
32
32
float
32
32
double
64
64
long double
64/80/128
128
 
         当编写同时针对两种平台的代码时,有一点非常重要,就是要清楚地定义哪一个对象该有固定位宽(即定宽类型),而先不要管目标平台架构。在以下情况中,可能需要定宽数据类型:
² 从网络连接中读取数据
² 32位程序写入到磁盘上的数据
² 某些二进制标准的接口
 
至于定宽数据类型,可使用定义在<intytpes.h>中的标准typedef。
int8_t   int16_t   int32_t   int64_t //有符号的
uint8_t   uint16_t   uint32_t   uint64_t //无符号的
 
严格来说,<inttypes.h>是一个C99标准的头文件,但大多数的C++编译器现在都支持它。
 
 
         平台相关数据类型的自动映射
         C和C++定义了如size_t、ptrdiff_t、fpos_t、time_t等等的typedef,抽象表示了对象的底层类型,因此可使编译器自动地把它们映射为合适的本地数据类型。请记住,在跨两种平台编码时,切记使用以上的typedef类型。拿size_t举例来说,在ILP32中,它定义了一个32位的无符号整型,而在LP64中,它定义了一个无符号的64位整型。
         如<fstream>之类的高级库通常都提供了更干净利落的移植,因为它们使用了抽象层。举例来说,函数tellp()返回一个std::streampos对象而不是一个int;而fstream::write()是按如下方式声明的:
typedef size_t streamsize;
basic_ostream<charT,traits>& write(const char_type* s, streamsize n);
 
         这就是说,你仍然能够把int、long这样的基本类型当作循环计数器、数组下标、文件描述符等等使用。
 
 
         指针的问题
         某些库会把指针转换成整型,亦或把整型转换成指针。比如说标准<signal()>函数:
handler signal(int signum, handler);
        在此,handler可以是一个指向函数的指针,或是两个整型常量SIG_DFLSIG_IGN其一,POSIX <dlfcn.h>库在它的dlsym()函数中也采取这种用法。此处的问题是,指针大小是依赖于相关平台的,所以当然不会把一个64位指针存入到一个int中。推荐的做法是,使用intprt_tunintptr_t这样的typedef,它们可作为安全保存指针的整型数据类型。
 
 
         ABI的问题
         应用程序二进制接口(ABI)指定了一种编程语言实体的二进制表示形式,包括名称改编(Name Mangling)或者名称修饰(Name Decoration)方案、内存排列和默认对齐方式等等。请看如下结构:
struct Record{
 long idx;
 bool cached;
};
 
         在一个典型的32位系统上,Record占8字节,然而,在64位系统上,会增加到12或16字节,因此,千万不要假定数据结构的大小是不变的,即使它是由定宽的数据成员组成,而目标平台的对齐方式仍会影响到它的总体大小。甚至抽象类也受ABI问题的影响:
 
class NetInterface
{
public:
 int virtual Connect(enum conn_type)=0;
 //其他成员……
 virtual ~NetInterface()=0;
};
 
         NetInterface类不包含非公有成员,像所有的多态类一样,它包含一个隐式的vptr,而其大小是依赖于特定平台的。同样地,函数的名称改编也是依赖于ABI的。以上表明,如果意外地把32位目标文件链接至64位库,或把64位目标文件链接至32位库,编译器都会给出明显的错误信息。
 
 
         I/O格式化
         如printf()一类的函数通常使用格式化标志,来控制输出的对齐、符号和宽度。请谨记以下守则:对指针使用“%p”标志、对类型参数使用 l- 前缀、对long的无符号类型使用 ul-,不管是使用<iostream>或是<stdio.h>,都应检查是否有足够的空间保证输出。最后,必须保证对unsigned long有20个字符及对指针有18个16进制字符的缓冲区。
 
         由以上可以看到,只需对数据类型稍做调整,完美跨越32位及64位平台,并不是件难事!
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值