12可移植性(兼容性)

来源:互联网 发布:python datetime 时区 编辑:程序博客网 时间:2024/05/22 17:46

12可移植性(兼容性)

本章讨论C++可移植性问题主要关注:32位移植到64位,不同CPU架构之间的移植。

移植中一些关键问题如下:

1.  指针截断

2. 数据类型字节对齐

3. 对内存地址的错误假设

4. 对复合数据类型成员地址的错误假设

5. 大小端,网络字节序问题
建议12.1 不直接使用C++的基本数据类型,不要假定其存储尺寸长度

说明:C++标准没有明确基本数据类型的大小与存储格式,这些基本类型包括:short,int, long, long

long, float double等。这些基本数据类型在不同的编译器中,实现有所不同,如:

long类型在32位编译模式下为4字节长度,在64位编译模式下为8字节长度。

所以建议不要直接使用基本数据类型。推荐如下两种使用方式:

1、重定义基本数据类型

    typedef int32_t int;
    typedef int64_t long long;

使用重定义后的基本类型好处是:如果程序需要移植,可以大大减少移植的工作量。

2、使用C99标准中定义的标准类型

    int64_t my_value = 0x123456789LL;
    uint64_t my_mask = 3ULL << 48;
使用这些标准类型长度的好处是,它们规定了固定的长度,这个长度不会随编译器变化而变化,所以

我们可以放心的使用。

建议12.2 避免指针截断

说明:指针截断是从32位移植到64位系统时,经常会碰到的问题。

    int *i = &int_val;
    short *w = (short*)((int)i + 2);
上面的代码在32位环境下运行是没有问题的,但在64位环境下,发生了地址截断:无法把64位长的数

据接到32位的数据空间里面。

上面的代码中可以使用intptr_t类型解决:

    int *i = &int_val;
    short *w = (short*)((intptr_t)i + 2);

建议12.3 注意数据类型对齐问题

说明:需要对结构对齐加以留心,尤其是对于存储在磁盘上的结构体。

在64位系统中,任何拥有int64_t/uint64_t成员的类/结构体将默认被处理为8字节对齐。如果32位和64                   位

代码共用磁盘上的结构体,需要确保两种体系结构下的结构体的一致对齐。

另外,大多数编译器提供了调整结构体对齐的方案:

gcc 中可使用__attribute__((packed)),MSVC 提供了#pragma pack()和__declspec(align()) 。

由于各个平台和编译器的不同,所以在不同编译器与平台移植代码时,一定要特别关注编译器关于对

齐的参数设置与默认值。因为字节对齐不仅影响性能,而且会导致一些不可预知的问题。

建议12.4 在涉及网络字节序处理时,要注意进行网络字节序与本地字节序的转换

说明:小端法(Little-Endian)

低位字节排放在内存的低地址端即起始地址,高位字节排放在内存的高地址端。

大端法(Big-Endian)

高位字节排放在内存的低地址端即起始地址,低位字节排放在内存的高地址端。
不同cpu平台上字节序通常也不一样:

X86、AMD64平台使用小端法、而HP-IA, IBM AIX的CPU采用的是大端法。

而网络字节序是大端法,如常见网络发送的码流。

涉及网络字节需要注意处理网络字节序与本地字节序的转换,即使本地字节序采用的也是大端法,为

了程序可移植性,建议也调用转换函数进行转换。

库函数提供了16,32位整型int的网络字节序与本地字节序的转换函数:

    htonl, htons, ntohl, ntohs - convert values between host and network byte order

建议12.5 避免无符号数与有符号数的转换

说明:不同的国际标准(ANSI   C/ISO  C++等)对隐式转换有符号和无符号类型的规则不同,有可能导致

不同的执行结果。

    unsigned short usNumber = xxx;
    long lNum = usNumber;
将unsigned   short赋值给long需要经过两次类型转换,ANSI标准中没有规定多次类型转换的顺序。大多

数编译器(例如VC)在高位优先填充0,按照下面的顺序进行转换:

      lNum = (long) (unsigned long) usNumber;

个别编译器(例如BSD的一些编译器)在高位优先填充1,即使用下面的顺序进行转换:

      lNum = (long) (signed short) usNumber;

如果是后一种转换顺序,并且正好usNumber 的高位为1,则首先被转换成一个负数的long,接着转换成

unsigned long时就成了很大的数。

usNumber永远不可能为负数,没有必要使用signed修饰。

修改办法是定义lNum的类型为unsigned long,并更改名字为ulNum:

    unsigned short usNumber = xxx;
    unsigned long ulNum = usNumber;
尽量避免无符号数与有符号数的转换,特别是长度不同数值的类型转换。请首先考虑设计上是否需要

这种转换。

建议12.6 创建64 位常量时使用LL 或ULL 作为后缀

说明:指定LL或ULL后缀说明,能让代码更加清晰。

    int64_t my_value = 0x123456789LL;
    uint64_t my_mask = 3ULL << 48;
尤其是在>>操作时,无符号与有符号有很大差异的,如果操作数是无符号数,则右移操作符>>,从左

边开始插入0, 否则插入符号位的拷贝或者插入0                ,这由编译器决定。

建议12.7 区分sizeof(void *)和sizeof(int)

说明:64位下sizeof(void *) != sizeof(int),而在32位下是相同的。

如果需要一个指针大小的整数请使用intptr_t。

建议12.8 编译器,操作系统相关的代码独立出来

说明:为了程序的可移植性,建议将编译器,操作系统相关的代码从产品代码中独立出来。

编译器特有的东西,如gcc的编译参数__thread,            __attribute__等,如果需要做到支持多个平台,需

要封装为宏或函数。
例如gcc的__thread对应的VC开关为__declspec(thread)

    __thread int number;
    __declspec(thread) int number;
均表示number为线程私有存储变量,可采用如下宏来封装:

    #ifdef WIN32
    #define THREAD __declspec(thread)
    #else
    #define THREAD __thread
    #endif

    THREAD int number;