PAYATHON 2026

C 中用 mmap() 内存进行类型双关会触发未定义行为吗?

数据阿明

结论

这段代码不能整体视为安全。开头用 memcpy 写入计数值,之后再按 int 访问,在 Linux 的常见实现中通常不会违反严格别名规则。但代码把字符串区域转换为 short * 后读取,可能违反 C 的有效类型规则,导致未定义行为。对齐和字节序都无法消除这个问题。

逐段看代码

在 Linux 上,mmap() 返回的地址按页对齐,通常满足 int 和 short 的对齐要求。但代码没有检查映射是否失败。若返回 MAP_FAILED,后续访问就会出错。此外,C 标准没有规定 mmap(),其行为取决于 Linux 等平台的实现。

下面的操作把计数值从一个 int 对象复制到映射内存:

int char_size = 5;
memcpy(page, &char_size, 4);

对于没有声明类型的存储,C 的有效类型规则会考虑 memcpy 源对象的类型。因此,在适用的实现和前提下,之后通过 int 左值访问这段存储有类型依据,严格别名规则通常不是问题。这里应使用 sizeof char_size,不要假定 int 一定是 4 字节。concat 中对应的硬编码偏移也应根据布局明确处理。

问题在字符串区域。代码用 memcpy 将字符数据写入 page + 4,随后又执行:

short data = *(short*)(page + 6);

这段区域存放的是字符数据。将它转换为 short * 再读取,不会因为地址对齐或硬件支持这种读取就符合 C 的有效类型规则,因此可能产生未定义行为。严格别名规则关注的是 C 抽象机中的对象访问类型,而不只是 CPU 能否完成内存读取。

之后通过 short * 写入,也会改动这两个字节。随后用字符指针读取对象表示是允许的,但字符串内容已经改变,显示结果还取决于字节序。此外,printf("%s", ...) 要求该区域仍有可访问的 NUL 终止符。

用 memcpy 读写短整数

如果要把当前位置的字节解释为本机 short,可以先复制到一个实际声明为 short 的对象中;写回时则反向复制:

short data;
memcpy(&data, page + 6, sizeof data);
printf("%X\n", (unsigned)data);

 data = 0x7777;
memcpy(page + 6, &data, sizeof data);

这样可以避免通过不兼容的 short * 左值访问字符串存储。不过,这并没有定义跨平台的数据格式:结果仍取决于 short 的大小、表示方式和字节序。如果这些字节属于文件、网络协议或固定布局,应按格式逐字节编码和解码,并明确规定字节序和字段宽度。

还需处理的边界

  • mmap() 后先检查返回值是否为 MAP_FAILED,完成后调用 munmap()。
  • 用 sizeof 表达对象大小。除非代码明确限定目标平台和数据格式,否则不要假定 int 固定为 4 字节。
  • 确保映射范围足以容纳计数、字符串和结尾的 NUL 字节,并避免长度计算溢出。
  • 如果要按 int 访问计数,字段偏移必须满足 int 的对齐要求,写入和读取也要遵循同一布局。

mmap() 提供可写映射,并不意味着可以忽略有效类型规则。需要在字节区域和整数值之间转换时,使用 memcpy 通常更简单稳妥。

备注:内容仅供参考。