OpenHarmony-64bits-coding-guide.md 31.7 KB
Newer Older
A
aqxyjay 已提交
1
# OpenHarmony 32/64位可移植编程规范
2 3 4 5 6 7 8 9

## 前言

### 目的

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

Mr-YX's avatar
Mr-YX 已提交
10
1. 轻量系统(mini system),面向MCU类处理器(例如Arm Cortex-M、RISC-V 32位)的轻量设备,硬件资源极其有限,支持的设备最小内存为128KiB
11 12 13
2. 小型系统(small system),面向应用处理器(例如Arm Cortex-A 64位)的设备,支持的设备最小内存为1MiB
3. 标准系统(standard system),面向应用处理器(例如Arm Cortex-A 64位)的设备,支持的设备最小内存为128MiB

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

### 适用范围

用户态和内核态的C、C++代码,不区分语言的标准。
19 20 21 22 23

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

#### 数据类型差异

Mr-YX's avatar
Mr-YX 已提交
24
大部分的32位系统采用的是ILP32,即int、long和pointer是32位长度。大部分的64位系统采用的是LP64,即long、long long、pointer是64位长度。Windows系统采用的是LLP64,即long long和pointer是64位长度。各系统基本数据类型长度对比如下表所示:
25 26 27 28 29 30 31 32 33 34 35 36 37

| **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         |

Mr-YX's avatar
Mr-YX 已提交
38
上表中只包含了部分基本类型,下表分别将ILP32和LP64的sizeof和print进行对比,展示了更全面的常量和类型对应的差异:
39

Mr-YX's avatar
Mr-YX 已提交
40
| Type               | ILP32 sizeof | ILP32 print | LP64 sizeof | LP64 print | 备注    |
41 42
| ------------------ | ------------ | ----------- | ----------- | ---------- | ------ |
| bool               | 1            | %u          | 1           | %u         | C++    |
Mr-YX's avatar
Mr-YX 已提交
43
| char               | 1            | %d或%c      | 1           | %d或%c     |         |
44 45 46 47 48
| 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         |        |
Mr-YX's avatar
Mr-YX 已提交
49 50 51 52
| long               | 4            | %ld         | **8**       | %ld        | 有差异  |
| unsigned long      | 4            | %lu         | **8**       | %lu        | 有差异  |
| long int           | 4            | %ld         | **8**       | %ld        | 有差异  |
| unsigned long int  | 4            | %lu         | **8**       | %lu        | 有差异  |
53 54
| long long          | 8            | %lld        | 8           | %lld       |        |
| unsigned long long | 8            | %llu        | 8           | %llu       |        |
Mr-YX's avatar
Mr-YX 已提交
55
| type \*            | 4            | %p          | **8**       | %p         | 有差异  |
56 57
| pid_t              | 4            | %d          | 4           | %d         |        |
| socklen_t          | 4            | %u          | 4           | %u         |        |
Mr-YX's avatar
Mr-YX 已提交
58 59 60 61 62
| 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        | 有差异  |
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97

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

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

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

## 编程指导

### 总则

A
aqxyjay 已提交
98
#### 【规则】开发者贡献的代码应当遵循此规范,编写出可同时应用于32位和64位的代码
99

Mr-YX's avatar
Mr-YX 已提交
100
【说明】由于OpenHarmony会长期同时存在32位的运行环境和64位的运行环境,为了代码的一致性,开发者在编码时需要充分考虑代码的可移植能力。
101 102 103 104 105

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

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

A
aqxyjay 已提交
106
【说明】基于可移植性要求,在32位和64位条件下,可变长度的数据类型可能导致兼容性错误,为简单清晰,要求采用归一清晰的数据类型进行定义。基于当前的要求,定义下列基础数据类型:
107 108 109 110 111 112 113 114 115 116 117

| 类型定义         | 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                                             |
W
wusongqing 已提交
118 119
| int64_t          | 8     | 8     | %PRId64 | 代替long long、宏实现代码兼容                                |
| uint64_t         | 8     | 8     | %PRIu64 | 代替unsigned long long、宏实现代码兼容                        |
120 121 122 123
| float            | 4     | 4     | %f      | 单精度浮点数                                                 |
| double           | 8     | 8     | %lf     | 双精度浮点数                                                 |
| bool             | 1     | 1     | %d      | 布尔类型                                                     |
| uintptr_t        | **4** | **8** | %zu     | 会根据32位和64位的不同定义为不同的长度,用于可能存储指针的场景 |
Mr-YX's avatar
Mr-YX 已提交
124
| type \*           | **4** | **8** | %p      | type \*,可变长度类型,与uintptr_t等价,存在类型转换时建议使用uintptr_t |
125 126 127
| nullptr_t        | **4** | **8** | %p      | 指针初始化                                                   |
| pid_t            | 4     | 4     | %d      | Linux内置,固定长度                                          |
| socklen_t        | 4     | 4     | %u      | Linux内置,固定长度                                          |
Mr-YX's avatar
Mr-YX 已提交
128
| off_t/time_t     | **4** | **8** | %zd     | 可变长度类型,有符号                                           |
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
| 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以适应不同的位宽

W
wusongqing 已提交
173
【说明】uintptr_t类型用于用于存储指针长度的数据,其长度在32位和64位可自动适应。
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264

【示例】

```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的范围常数,增加该参数没有意义,应当避免使用 |
W
wusongqing 已提交
265
| 0x8000000000LL | 8         | 8     | 后缀为LL,uint64_t类型                                       |
266

A
aqxyjay 已提交
267
从上表中可看出,使用L或UL后缀的常量,其长度在32位和64位下发生变化,不利于代码的可移植性,因此禁止使用这个后缀。
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355

【示例】

```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
Mr-YX's avatar
Mr-YX 已提交
356 357
#pragma pack(push) // 保存当前的对齐方式
#pragma pack(1) // 设置对齐方式为1字节对齐
358 359 360 361
struct test
{
    ......
};
Mr-YX's avatar
Mr-YX 已提交
362
#pragma pack(pop) // 恢复之前的对齐方式
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
```

#### 【规则】涉及多机通信的消息结构体,需要对齐统一。基于兼容性考虑,可优先采用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 *等单字节指针类型

Mr-YX's avatar
Mr-YX 已提交
497
【说明】如果转换为整型,采用uint32_t强转,会出现指针被截断的情况,因此需要转换为uintptr_t。也可转换为单字节宽度的指针类型,如uint8_t \*、char \*等,转为上述方式后,偏移均被理解为字节。void \*类型实际也会按照1字节进行偏移,但为了类型明确化,建议使用前面更明确的类型代替。
498 499 500 501 502 503 504 505

【示例】

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

// 正确
Mr-YX's avatar
Mr-YX 已提交
506
void *pPkt = (void *)((uintptr_t)MSG_GET_DATA_ADDR(msgAddr) + OFFSET); // C
507 508 509 510 511
void *pPkt = reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(MSG_GET_DATA_ADDR(msgAddr)) + OFFSET); // C++
```

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

Mr-YX's avatar
Mr-YX 已提交
512
【说明】如果要定义的变量可能是不确定长度的指针,使用void \*;如果要定义的变量既可以是指针,也可以是整形,使用uintptr_t。
513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535

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

```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); // 错误
```

W
wusongqing 已提交
536
strlen返回size_t(它在LP64中是unsigned long),当赋值给一个int32_t时,截断是必然发生的。而通常,截断只会在str的长度大于2GB时才会发生,这种情况在程序中一般不会出现,容易忽略。
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590

#### 【规则】在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位的

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