# OpenHarmony 32/64位可移植编程规范

## 前言

### 目的

OpenHarmony的目标是面向全场景、全连接、全智能时代，基于开源的方式，搭建一个智能终端设备操作系统的框架和平台，促进万物互联产业的繁荣发展。具有“硬件互助，资源共享”、“一次开发，多端部署”、“统一OS，弹性部署”的技术特性。
OpenHarmony支持三种系统类型：

1. 轻量系统（mini system），面向MCU类处理器（例如Arm Cortex-M、RISC-V 32位）的轻量设备，硬件资源极其有限，支持的设备最小内存为128KiB
2. 小型系统（small system），面向应用处理器（例如Arm Cortex-A 64位）的设备，支持的设备最小内存为1MiB
3. 标准系统（standard system），面向应用处理器（例如Arm Cortex-A 64位）的设备，支持的设备最小内存为128MiB

因此，OpenHarmony的代码运行在32位/64位的设备上。对系统代码的可移植性、32位/64位运行模式下的编码需要一定的规约。本文以此为初衷，结合OpenHarmony的特点，拟定了相关编程规约，用于指导代码移植和64位编码，提升代码的规范性及可移植能力，供研发人员参考。

### 适用范围

用户态和内核态的C、C++代码，不区分语言的标准。

### 32位/64位系统的类型差异

#### 数据类型差异

大部分的32位系统采用的是ILP32，即int、long和pointer是32位长度。大部分的64位系统采用的是LP64，即long、long long、pointer是64位长度。Windows系统采用的是LLP64，即long long和pointer是64位长度。各系统基本数据类型长度对比如下表所示：

| **TYPE**  | **ILP32** | **LP64** | **LLP64** | **LP32** | **ILP64** |
| --------- | --------- | -------- | --------- | -------- | --------- |
| char      | 1         | 1        | 1         | 1        | 1         |
| short     | 2         | 2        | 2         | 2        | 2         |
| int       | 4         | 4        | 4         | 2        | 8         |
| long      | **4**     | **8**    | **4**     | 4        | 8         |
| long long | 8         | 8        | 8         | 8        | 8         |
| float     | 4         | 4        | 4         | 4        | 4         |
| double    | 8         | 8        | 8         | 8        | 8         |
| size_t    | **4**     | **8**    | **8**     | 4        | 8         |
| pointer   | **4**     | **8**    | **8**     | 4        | 8         |

上表中只包含了部分基本类型，下表分别将ILP32和LP64的sizeof和print进行对比，展示了更全面的常量和类型对应的差异：

| Type               | ILP32 sizeof | ILP32 print | LP64 sizeof | LP64 print | 备注    |
| ------------------ | ------------ | ----------- | ----------- | ---------- | ------ |
| bool               | 1            | %u          | 1           | %u         | C++    |
| char               | 1            | %d或%c      | 1           | %d或%c     |         |
| unsigned char      | 1            | %u          | 1           | %u         |        |
| short              | 2            | %d          | 2           | %d         |        |
| unsigned short     | 2            | %u          | 2           | %u         |        |
| int                | 4            | %d          | 4           | %d         |        |
| unsigned int       | 4            | %u          | 4           | %u         |        |
| long               | 4            | %ld         | **8**       | %ld        | 有差异  |
| unsigned long      | 4            | %lu         | **8**       | %lu        | 有差异  |
| long int           | 4            | %ld         | **8**       | %ld        | 有差异  |
| unsigned long int  | 4            | %lu         | **8**       | %lu        | 有差异  |
| long long          | 8            | %lld        | 8           | %lld       |        |
| unsigned long long | 8            | %llu        | 8           | %llu       |        |
| type \*            | 4            | %p          | **8**       | %p         | 有差异  |
| pid_t              | 4            | %d          | 4           | %d         |        |
| socklen_t          | 4            | %u          | 4           | %u         |        |
| off_t              | 4            | %zd         | **8**       | %zd        | 有差异  |
| time_t             | 4            | %zd         | 8           | %zd        | 有差异  |
| pthread_t          | 4            | %zu         | **8**       | %zu        | 有差异  |
| size_t             | 4            | %zu         | 8           | %zu        | 有差异  |
| ssize_t            | 4            | %zd         | **8**       | %zd        | 有差异  |

#### 数据结构对齐的差异

##### 包含指针数据结构对齐的变化

```c
typedef struct tagFoo {
    void *p;
    uint32_t i;
} Foo;
```

在32位系统上，指针长度为4，Foo 4字节对齐，sizeof(Foo) 等于8，在64位系统上，指针长度为8，Foo 8字节对齐，sizeof(Foo) 等于16。

##### 包含64位整形的数据结构对齐的变化

```c
typedef struct tagFoo {
    uint64_t p;
    uint32_t i;
} Foo;
```

虽然uint64_t是定长的，但由于对齐的存在，Foo的大小也是不同的。在32位系统上，Foo 4字节对齐，sizeof(Foo) 等于12，在64位系统上，Foo 8字节对齐，sizeof(Foo) 等于16。uint64_t替换为double，上述结论依然成立。	

### 约定

**规则**：编程时必须遵守的约定(must)

**建议**：编程时应该遵守的约定(should)

## 编程指导

### 总则

#### 【规则】开发者贡献的代码应当遵循此规范，编写出可同时应用于32位和64位的代码

【说明】由于OpenHarmony会长期同时存在32位的运行环境和64位的运行环境，为了代码的一致性，开发者在编码时需要充分考虑代码的可移植能力。

### 数据类型定义和格式化

#### 【规则】应当使用统一定义的数据类型定义变量，无特殊意义或要求应当避免自行定义基本数据类型

【说明】基于可移植性要求，在32位和64位条件下，可变长度的数据类型可能导致兼容性错误，为简单清晰，要求采用归一清晰的数据类型进行定义。基于当前的要求，定义下列基础数据类型：

| 类型定义         | ILP32 | LP64  | PRINT   | 使用场景及代替类型                                           |
| ---------------- | ----- | ----- | ------- | ------------------------------------------------------------ |
| void             | -     | -     | -       | void，无类型，仅用于占位和通用指针定义                       |
| char             | 1     | 1     | %c      | 对于字符串、数组直接使用原生char                             |
| int8_t           | 1     | 1     | %d      | 对于1字节整型使用int8_t，uint8_t                             |
| uint8_t          | 1     | 1     | %u      | 对于1字节整型使用int8_t，uint8_t                             |
| int16_t          | 2     | 2     | %d      | 代替short                                                    |
| uint16_t         | 2     | 2     | %u      | 代替unsigned short                                           |
| int32_t          | 4     | 4     | %d      | 代替int                                                      |
| uint32_t         | 4     | 4     | %u      | 代替unsigned int                                             |
| int64_t          | 8     | 8     | %PRId64 | 代替long long、宏实现代码兼容                                |
| uint64_t         | 8     | 8     | %PRIu64 | 代替unsigned long long、宏实现代码兼容                        |
| float            | 4     | 4     | %f      | 单精度浮点数                                                 |
| double           | 8     | 8     | %lf     | 双精度浮点数                                                 |
| bool             | 1     | 1     | %d      | 布尔类型                                                     |
| uintptr_t        | **4** | **8** | %zu     | 会根据32位和64位的不同定义为不同的长度，用于可能存储指针的场景 |
| type \*           | **4** | **8** | %p      | type \*，可变长度类型，与uintptr_t等价，存在类型转换时建议使用uintptr_t |
| nullptr_t        | **4** | **8** | %p      | 指针初始化                                                   |
| pid_t            | 4     | 4     | %d      | Linux内置，固定长度                                          |
| socklen_t        | 4     | 4     | %u      | Linux内置，固定长度                                          |
| off_t/time_t     | **4** | **8** | %zd     | 可变长度类型，有符号                                           |
| size_t/pthread_t | **4** | **8** | %zu     | 可变长度类型，无符号，仅用于调用库函数的兼容性要求（比如底层API中使用了size_t） |

上述类型定义在stddef.h（C）和cstdint（C++）标准库中，在采用#define重定义相关类型时，其源头应来自于上述类型。

非特殊情况，不要使用非标准类型。禁止定义通用基础类型，除非定义的类型有明确的特定含义。对于涉及到第三方接口及API调用中使用的基础数据类型，以相关专项规则为准。

【示例】非必要禁止自定义基础数据类型：

```c
// 禁止使用下面代码来重定义
typedef unsigned int UINT32;// 此定义禁止使用

// 已经有内置的uint32_t完全可代替上述定义，因此上述定义没有存在的必要。但如果定义的类型有明确的专用意义，则可以保留定义：
typedef uint32_t DB_TABLE_ID; // 此定义可以保留
```

【示例】下面2个类型的长度与通常理解不一致，禁止使用：

| 类型定义 | ILP32  | LP64  | PRINT | 使用场景及代替类型     |
| -------- | ------ | ----- | ----- | ---------------------- |
| float_t  | **12** | **4** | -     | 长度不合常理，禁止使用 |
| double_t | **12** | **8** | -     | 长度不合常理，禁止使用 |

#### 【建议】应当避免使用非统一定义的可变长类型定义变量，为适配平台、第三方代码接口等使用，需要特殊说明

【说明】原生的long、int、short、size_t等类型，在64位和32位下其长度不确定，在编码时容易疏忽而导致代码隐患。如果将此类型用于外部存储或通信，则很容易导致系统间的不兼容，因此无特殊需求原则上应当避免使用这些类型。

【例外】如果因为平台、第三方、库函数等原因，可以有限使用。使用这些类型定义时需要增加说明方便理解。

【示例】

```c
long var;
// 该定义在64位下为8bytes，在32位下为4bytes，存在歧义，建议修改为uint32_t或uint64_t。
```

#### 【规则】避免使用uchar类型定义变量

【说明】uchar或unsigned char用来定义字符串是一种不规范用法。对当前代码中已经存在的使用，需要修改为char类型（确认是字符串）或uint8_t（确认是无符号整数）。

对于涉及到8bit编码的非ANSI字符序列，建议仍然使用char类型来定义。C++情况下，可使用wchar等宽字符类型定义。

#### 【规则】当需要采用整型变量来存储指针时，变量应该定义成uintptr_t以适应不同的位宽

【说明】uintptr_t类型用于用于存储指针长度的数据，其长度在32位和64位可自动适应。

【示例】

```c
uintptr_t sessionPtr;

// 将指针存储为变量时，强转和左值都应定义为uintptr_t，以适配不同场景字长的变化
sessionPtr = (uintptr_t) GetMemAddress();
```

#### 【建议】函数入参或返回值定义的类型与变量类型发生不匹配时候，需要谨慎处理，以保证按照赋值和类型转换的规则进行转换后的结果正确

【说明】如果不可避免出现了类型不一致的情况，需要谨慎进行类型转换，确保转换后的结果满足实际应用需求。

【示例】

```c
long function (long l);
int main () {
    int i = -2;
    unsigned int k = 1U;
    long n = function(i + k);
}
```

【注释】上面这段代码在 64 位系统上会失败，因为表达式 (i + k) 是一个无符号的 32 位表达式，在将其转换成 long 类型时，符号并没有得到扩展。入参的结果是不正确的。解决方案是将其中一个操作数强制转换成 64 位的类型。

#### 【规则】打印64位的整数请使用%PRId64，%PRIu64，%PRIx64等64位兼容宏进行格式化输出，不允许使用%d，%ld，%zd，%x，%lx等不兼容输出

【说明】如果待格式化的数据明确为64位类型（定义为uint64_t），其输出格式化应当采用下述方法：

```c
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main()
{
    uint64_t a = 0x1234567fffffff;
    printf("a = %"PRIx64"\n", a);
    return 0;
}
```

上述输出代码，在32位和64位环境下均能够正常输出64位长度数字。如果采用其它格式处理，都存在兼容性问题，需要注意避免，如下表：

| 格式化方法 | ILP32构建      | ILP32结果 | LP64构建       | LP64结果 | 结论       |
| ---------- | -------------- | --------- | -------------- | -------- | ---------- |
| %lx        | 类型不匹配告警 | 错误      | 无告警         | 正确     | **不兼容** |
| %zx        | 类型不匹配告警 | 错误      | 无告警         | 正确     | **不兼容** |
| %llx       | 无告警         | 正确      | 类型不匹配告警 | 正确     | **不兼容** |
| %p         | 类型不匹配告警 | 错误      | 类型不匹配告警 | 正确     | **不兼容** |
| %PRIx64    | 无告警         | 正确      | 无告警         | 正确     | 兼容       |

【示例】类型不匹配告警信息示例：

```bash
# 32位编译
format ‘%lx’ expects argument of type ‘long unsigned int’, but argument 2 has type ‘uint64_t {aka long long unsigned int}’
format ‘%zx’ expects argument of type ‘size_t’, but argument 2 has type ‘uint64_t {aka long long unsigned int}’
format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘uint64_t {aka long long unsigned int}’

# 64位编译
format ‘%llx’ expects argument of type ‘long long unsigned int’, but argument 2 has type ‘uint64_t {aka long unsigned int}’
format ‘%p’ expects argument of type ‘void *’, but argument 2 has type ‘uint64_t {aka long unsigned int}’
```

#### 【规则】打印输出可变类型数据，对齐时要考虑数据长度并预留足够空间

【说明】32位下指针及size_t长度最大只有8位（16进制）或10位（10进制），但在64位系统下，其最大宽度可达到20位，因此在打印输出时需要充分考虑其范围，避免打印因不对齐而影响用户体验。

#### 【规则】使用常量时，禁止采用L/UL作后缀，允许增加U后缀指定为unsigned int类型，允许增加LL/ULL后缀指定其长度为64位

【说明】无后缀的常量缺省为int类型；采用L/UL为后缀时，在32位和64位系统下，长度可能发生变化；确定需要64位时，采用LL/ULL为后缀定义以确保在32位和64位系统下均为64位长度。

| 常量定义       | ILP32     | LP64  | 使用场景                                                     |
| -------------- | --------- | ----- | ------------------------------------------------------------ |
| 1              | 4         | 4     | 默认为int32_t类型，长度固定                                  |
| 1U             | 4         | 4     | 默认为uint32_t类型，长度固定                                 |
| 1L             | **4**     | **8** | 后缀为L或UL，长度不同，应当避免使用                          |
| 1UL            | **4**     | **8** | 后缀为L或UL，长度不同，应当避免使用                          |
| 1LL            | 8         | 8     | 默认为int64_t类型，长整形数据，直接使用LL，确定为64位，长度固定 |
| 1ULL           | 8         | 8     | uint64_t类型，无符号长整形数据                               |
| 0x7FFFFFFF     | 4         | 4     | 不携带附加符号的数字，不超过int32_t的范围，默认为int32_t类型 |
| 0x7FFFFFFFL    | **4**     | **8** | 长度不同，避免使用                                           |
| 0x7FFFFFFFLL   | 8         | 8     | 长度固定                                                     |
| 0x80000000     | 4         | 4     | 小于uint32_t范围，类型为uint32_t类型                         |
| 0x80000000L    | **4**     | **8** | 后缀为L，小于uint32_t范围，增加该参数没有意义，应当避免使用  |
| 0x80000000LL   | 8         | 8     | 增加LL后缀，小于uint32_t范围，长度固定                       |
| 0x8000000000   | **NA或8** | **8** | 无后缀，超过uint32_t的范围，编译器默认为LL或无效，64位下固定为uint64_t类型 |
| 0x8000000000L  | **NA或8** | **8** | 后缀为L，对超过uint32_t的范围常数，增加该参数没有意义，应当避免使用 |
| 0x8000000000LL | 8         | 8     | 后缀为LL，uint64_t类型                                       |

从上表中可看出，使用L或UL后缀的常量，其长度在32位和64位下发生变化，不利于代码的可移植性，因此禁止使用这个后缀。

【示例】

```c
// 下面定义中的UL是没有意义的，在32位系统会出错，在64位系统不需要
#define YYY_START_ADDRESS(XXX_START_ADDR + 0x80000000UL)
```

#### 【规则】应当使用标准头文件中定义的宏常量，避免自行定义

【说明】C（stdint.h）和C++（cstdint.h）的标准头文件中均有定义最大值/最小值的宏常量，源文件中引用即可，避免自行定义。

【示例】

```c
#include <cstdio>
#include <cinttypes>
 
int main()
{
    std::printf("%zu\n", sizeof(std::int64_t));
    std::printf("%s\n", PRId64);
    std::printf("%+" PRId64 "\n", INT64_MIN);
    std::printf("%+" PRId64 "\n", INT64_MAX);

    std::int64_t n = 7;
    std::printf("%+" PRId64 "\n", n);
}
```

#### 【规则】应当使用统一的定义表示常量的无效值，避免直接使用全F等表示方法以适应不同的长度

【说明】同一个数字常量值在64位系统和32位系统上，按照32位系统有无符号类型，理解的结果可能不一样；特别是32位系统上最高bit位为1的，在32位系统上按照有符号类型理解成一个负数，在64位系统上是一个正数。要求统一使用typedef.h中的统一定义，表示数据的无效值。

```c
// 使用typedef.h中的定义
#define INVALID_INT8((int8_t)(-1))
#define INVALID_UINT8((uint8_t)(-1))
#define INVALID_INT16((int16_t)(-1))
#define INVALID_UINT16((uint16_t)(-1))
#define INVALID_INT32((int32_t)(-1))
#define INVALID_UINT32((uint32_t)(-1))
#define INVALID_INT64((int64_t)(-1))
#define INVALID_UINT64((uint64_t)(-1))
```

【示例】

```c
// 在32位系统上，n是一个负数
long n = 0xFFFFFFFF;
// 在64位系统上，n是一个正数，实际值为0x00000000FFFFFFFF。
long n = 0xFFFFFFFF;
```

#### 【建议】对不会超过32位空间大小的临时变量，建议采用uint32_t定义

【说明】定义的变量不需要特别关注其类型，可采用默认的uint32_t类型，以减少因类型不一致导致的大量强制类型转换。

### 结构体对齐与填充

#### 【规则】代码中禁止采用常量硬编码来指定变量长度、结构体长度，应当使用sizeof等内建类型来获取

【说明】sizeof可自动计算相关变量和结构的长度，避免硬编码错误；同时在编译时即完成了所属变量长度的计算，不会影响运行性能。

在64位系统下默认对齐方式是8字节对齐，使用sizeof()获取结构体长度来取代硬编码，以避免因结构对齐导致的长度计算错误。

联合在使用时，需要特别关注到其长度在64位和32位下的不同，避免在计算长度时出现错误。特别是按照最大长度进行计算和空间申请。

【示例】未采用sizeof计算结构长度，可能导致内存空间不足。

```c
int32_t *p;
p = (int32_t *)malloc(4 * ELEMENTS_NUMBER);
// 这行代码假定指针的长度为4字节，而这在LP64中是不正确的，此时是8字节。

//正确的方法应使用sizeof()
int32_t *p;
p = (int32_t *)malloc(sizeof(p) * ELEMENTS_NUMBER);
```

#### 【建议】特殊情况下，64位系统可以强制编译器指定对齐方式

【说明】在需要时，为保持代码兼容性，可采用指定对齐方式。使用伪指令#pragma pack (n)，编译器将按照n 个字节对齐；使用伪指令#pragma pack ()，取消自定义字节对齐方式。

【样例】

```c
#pragma pack(push) // 保存当前的对齐方式
#pragma pack(1) // 设置对齐方式为1字节对齐
struct test
{
    ......
};
#pragma pack(pop) // 恢复之前的对齐方式
```

#### 【规则】涉及多机通信的消息结构体，需要对齐统一。基于兼容性考虑，可优先采用1字节对齐；禁止使用8字节对齐和64位数据类型以避免与32位系统通信错误

【说明】板间通信消息涉及到跨板操作，除非所有软件同步升级，否则将会导致通信失败。为兼容性考虑，对已经存在的协议和结构，保持1字节对齐，并转换为网络序。对新增的通信协议，为通信效率和处理性能考虑，可以有条件采用4字节对齐。

#### 【规则】对外提供的接口数据结构应当避免出现填充，至少要求采用4字节对齐

【说明】对功能特性对外提供的API头文件，如果其中涉及到结构体定义，应当避免结构体填充，建议能够在自然对齐情况下数据不出现空洞，至少需要保证4字节对齐。

#### 【建议】应当使用成员名称访问结构体成员，禁止使用偏移方式访问

【说明】数据结构成员的偏移在32位和64位对齐情况下，其偏移值不同，不能直接计算每个成员的大小之后作为偏移；在32位系统上没有自动填充的数据结构，到了64位上有可能出现自动填充。

【示例】

```c
Struct A
{
    uint32_t a;
    uint32_t *p;
    uint32_t b;
};
```

成员b的偏移等于sizeof(a) + sizeof(p)在32位系统上成立，在64位系统上不成立。正确的做法应当是直接通过变量名称进行索引定位：

```c
xxx.b = 123;
```

如果结构定义比较特殊，比如结构体只是消息的头部，后续还有其它字段，则可能需要重新定义此结构，使结构体内部不出现填充字段。

【示例】

```c
typedef struct {
    uint32_t self;           /* 其它对齐结构 */
    uint32_t brother;        /* 其它对齐结构 */
    uint8_t processedFlag;   /* 当前节点是否被处理过的标志 */
    uint8_t reserve[3];      /* 为了4字节对齐的保留字段 */
} TreeNodeInfo;

typedef struct {
    TreeNodeInfo nodeInfo;   /* 每一个node的树信息的数据结构 */
    void *userInfo;          /* 每一个node的用户信息数据结构 */
} TreeNode;
```

TreeNode结构体中有两个成员结构体，下面的代码中根据第二个成员来获取第一个成员的地址，采用第二个成员首地址减去sizeof(第一个成员)计算（inUserInfo指向结构体中的userInfo字段）：

```c
inTreeNodeInfo = (TreeNodeInfo *)((void *)(((char *)inUserInfo) - sizeof(TreeNodeInfo)));
```

结构体采用自然对齐，需要注意到子结构体TreeNodeInfo，在32位下其长度为12bytes，在64位下，**其长度也是12位**。结构体TreeNode在32位下成员结构体之间无填充，其长度为16bytes，但在64位下的结构体长度：sizeof(TreeNodeInfo)=12，sizeof(TreeNode)=24，即子结构体TreeNodeInfo后有4个字节的填充字段，因此在64位下通过上述方法获取前一个字段地址，就会漏算填充的4个字节，导致成员访问出错。

#### 【建议】结构体定义时，为节省存储空间，在保证可读性的前提下，尽可能实现8字节自然对齐，避免出现填充

【说明】如果结构体能够实现自然对齐，则不需要进行填充，可有效节省结构体的无效空间。在不影响可读性的前提下，建议将size_t和pointer等64位长度类型放在结构体两端定义。

【示例】

```c
// 下面的结构体，在自然对齐情况下，其长度为24bytes
struct Foo
{
    int32_t a;
    uint64_t l;
    int32_t x;
}

// 经过适当调整，可优化到16bytes
struct Foo
{
    uint64_t l;
    int32_t a;
    int32_t x;
}
```

### 数据类型转换及运算

#### 【规则】应当避免不同类型的数据之间的隐式类型转换，如果必要，应当采用显式类型转换，避免在32位和64位系统结果不一致

【说明】谨慎处理不同长度和精度的操作数之间进行运算，避免因默认转换出现精度和符号的丢失。64位与32位混合编程情况下，需要关注隐式类型转换的几个重要点：

1. 64位长度的变量给32长度变量赋值，低32位直接赋值，高32位被截断丢失；低32位再根据左值变量类型理解成有符号或者无符号

2. 反之，32位长度的变量给64长度变量赋值，根据源32位为有符号或者无符号进行符号扩展，目的64位数再根据变量类型理解成有符号或者无符号。

3. 有符号数与无符号数进行运算，如果不指定结果类型，系统默认按照无符号方式理解。

上述转换过程可能带来不符合原意的结果，因此需要谨慎对待，尽可能避免隐式的转换。

【示例】转换后结果看上去正确的示例

```c
int32_t t1 = -2;
int64_t t2 = 1;
int32_t t3 = t1 + t2;
printf("t3 = %d\n", t3);

// 打印结果： t3 = -1
```

t1是个32位数，t2是个64位数，在做运算之前，先把t1也扩展成64位的。加法完成之后结果是一个64位的int64_t类型，在内存中16进制存储值为：0xffffffffffffffff，在给32位int32_t类型赋值发生截断， 值为0xffffffff，再理解成有符号32位，值为-1。此结果虽然看上去正确，但发生了数据截断，可能并不符合作者的本意。

【示例】有符号向无符号转换

```c
int64_t t1 = -1;
uint32_t t2 = t1;
printf("t2=%u", t2);

// 打印结果：t2=4294967295
```

t1是个64位int64_t类型，用二进制表示为0xffffffffffffffff，如果赋值给一个32位int类型，高32位丢失，直接显示位低32位的值，二进制为0xffffffff，按照无符号方式理解，值为4294967295。

【示例】32位向64位无符号转换

```c
int32_t t1 = -1;
uint64_t t2 = t1;
printf("t2 = %lu\n", t2);

// 打印结果：t2 = 18446744073709551615
```

源32位是个有符号的负数，扩展时，应该带符号扩展，负数高位全部为f，扩展后值为0xffffffffffffffff。目的64位类型是个无符号的数，所以其值是一个很大的正数。

#### 【规则】当需要将指针作为基址，再按照字节计算偏移时，指针需要强制转换为uintptr_t或uint8_t *等单字节指针类型

【说明】如果转换为整型，采用uint32_t强转，会出现指针被截断的情况，因此需要转换为uintptr_t。也可转换为单字节宽度的指针类型，如uint8_t \*、char \*等，转为上述方式后，偏移均被理解为字节。void \*类型实际也会按照1字节进行偏移，但为了类型明确化，建议使用前面更明确的类型代替。

【示例】

```c
// 错误
void *pPkt = (void *)((uint32_t)MSG_GET_DATA_ADDR(msgAddr) + OFFSET);

// 正确
void *pPkt = (void *)((uintptr_t)MSG_GET_DATA_ADDR(msgAddr) + OFFSET); // C
void *pPkt = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(MSG_GET_DATA_ADDR(msgAddr)) + OFFSET); // C++
```

#### 【规则】禁止指针与uint32_t之间相互赋值，包括函数参数传递

【说明】如果要定义的变量可能是不确定长度的指针，使用void \*；如果要定义的变量既可以是指针，也可以是整形，使用uintptr_t。

【示例】指针与整型的转换

```c
int32_t i, *p, *q;
p = &i;
q = (int32_t *) (int32_t)&i;

// 在A32架构下，p = q；但是在A64-LP64架构下，p != q
```

为了避免这种类型冲突问题，可以用uintptr_t来表示指针类型。

#### 【规则】禁止size_t与int32_t/uint32_t之间相互赋值，包括函数参数传递

【说明】可变长度类型禁止与32长度类型强转。

【示例】

```c
int32_t length = (int32_t)strlen(str); // 错误
```

strlen返回size_t（它在LP64中是unsigned long），当赋值给一个int32_t时，截断是必然发生的。而通常，截断只会在str的长度大于2GB时才会发生，这种情况在程序中一般不会出现，容易忽略。

#### 【规则】在64位环境下使用大数组或大for循环索引时，索引类型应当与下标边界保持一致

【说明】如果系统中使用了超大数组或超大循环，在64位环境有可能超过32位的索引范围，则需要使用可变长度类型或64位类型来定义数组下标或循环变量，避免因变量范围不足而导致不能全量遍历。

【示例】

```c
int64_t count = BIG_NUMBER; 
for (unsigned int index = 0; index != count; index++)
    ... 
```

在64位系统上，由于int64_t是64位类型，count是一个很大的数。unsigned int是32位类型 导致上面的循环永远不会终止。因此，index应该改成int64_t类型。

### 位域和字节序

#### 【规则】基于位域的变量定义需要充分考虑位域的基础类型及对齐问题，避免在不同的位宽下计算出错

【说明】位域的宽度需要结合对齐进行理解，结构体会变长；对位域的取值需要采用名称，避免采用直接计算。

#### 【规则】在64位系统上，考虑移位操作带来的长度差异，以及移位以后生成的值隐形扩展到64位的问题

【说明】位移运算时，需要检查是否可能导致溢出而回绕的情况，需要修改为32位类型避免结果不一致。

【示例】

```c
int64_t t = 1 << a; // 需考虑a的大小
```

在32位系统上，a的最大值是32，在64位系统上，是64。如果a是一个32位变量，其结果还要考虑到64位扩展问题。

### 第三方库和差异化功能

#### 【规则】64位系统使用到的第三方类库，都必须是支持64位的

【说明】部分库函数、第三方开源代码本身对64位的支持不定完善，需要针对所有涉及的代码进行评估，确保其可运行在64位环境下。部分库函数在64位和32位采用不同的接口实现，因此需要确保使用正确的接口。

【示例】内存映射处理，在32位系统下可使用mmap，如果映射超过2GBytes，则需要使用mmap64；但64位系统下可统一使用mmap。

#### 【规则】涉及到底层汇编的功能，需要在64位和32位系统分别独立调测，避免功能不可用

【说明】切换到64位后，寄存器个数和位宽都有变化，涉及到汇编的相关调测功能都需要进行重新的调测，并需要同时关注32位和64位下的代码有效性。

【示例】调用栈的推栈功能，在32位和64位内嵌的汇编代码需要独立编写调测。

#### 【规则】补丁功能需要同时支持32位指令和64位指令差异

【说明】由于指令长度变更，补丁机制及功能需要适配修改。

#### 【规则】64位系统使用到的工具，都必须是支持64位的

【说明】如果不可避免出现了类型不一致的情况，需要谨慎进行类型转换，确保转换后的结果满足实际应用需求。