【发布时间】:2017-07-25 18:14:26
【问题描述】:
考虑一个 POSIX.1-2008 兼容的操作系统,并让 fd 是一个有效的文件描述符(对于一个打开的文件、读取模式、足够的数据......)。以下代码符合 C++11 标准*(忽略错误检查):
void* map = mmap(NULL, sizeof(int)*10, PROT_READ, MAP_PRIVATE, fd, 0);
int* foo = static_cast<int*>(map);
现在,以下指令是否违反了严格的别名规则?
int bar = *foo;
按照标准:
如果程序尝试通过以下类型之一以外的左值访问对象的存储值,则行为未定义:
- 对象的动态类型,
- 对象动态类型的 cv 限定版本,
- 与对象的动态类型类似(如 4.4 中定义)的类型,
- 对应于对象动态类型的有符号或无符号类型,
- 一种有符号或无符号类型,对应于对象动态类型的 cv 限定版本,
- 一种聚合或联合类型,在其元素或非静态数据成员(递归地包括子聚合或包含联合的元素或非静态数据成员)中包含上述类型之一,
- 一种类型,它是对象动态类型的(可能是 cv 限定的)基类类型,
- char 或 unsigned char 类型。
map / foo 指向的对象的动态类型是什么?那还是一个对象吗?标准说:
类型 T 的对象的生命周期开始于:获得类型 T 的正确对齐和大小的存储,并且如果对象具有非平凡初始化,则其初始化完成。
这是否意味着映射的内存包含 10 个 int 对象(假设初始地址对齐)? 但如果是真的,这是否也适用于这段代码(这显然打破了严格的别名)?
char baz[sizeof(int)];
int* p=reinterpret_cast<int*>(&baz);
*p=5;
奇怪的是,这是否意味着声明 baz 会启动任何(正确对齐的)大小为 4 的对象的生命周期?
一些上下文:我正在映射一个包含我希望直接访问的数据块的文件。由于这个块很大,我想避免 memcpy-ing 到一个临时对象。
*这里可以用 nullptr 代替 NULL,它是否隐式转换为 NULL?标准中的任何参考资料?
【问题讨论】:
-
你的意思是
int* foo = static_cast<int*>(map);(不是fd) -
不幸的是它确实破坏了严格的别名,但另一方面,编译器无法知道该内存区域中存储的内容,因此它必须假设它可以是任何类型的对象并且因此你不应该看到任何奇怪形式的 UB。
-
@MikeMB 传递
-fstrict-aliasing标志,它将导致未定义的行为,甚至可能优化为NOP。将需要一个 memcpy 或至少一个执行未对齐读取的辅助函数来确保定义的行为。 -
@Yakk:
The compiler could know that doesn't create any C++ objects。如何?除了 malloc 或 memcpy,mmap 不是 c++ 标准库的一部分。并且没有理由相信 c 函数不能返回指向包含 c++ 对象的内存的指针。 -
@Yakk:Afaik,posix 规范中没有任何内容表明 mmap 返回指向原始内存的指针,与 malloc 相反,它保证被初始化,在这种情况下,内存甚至包含可能具有的实际数据以前是通过不同的映射编写的。那么你有什么权利附加这样的属性呢?最后,c 和 c++ 函数之间的交互是无论如何定义的 afaik 实现(更不用说 mmap 是一个 posix 函数),所以单独的 c++ 标准可能不会在这里给出最终答案。