mmap和C ++严格的别名规则

Ste*_*ven 2 c++ posix c++11

考虑一个符合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);
Run Code Online (Sandbox Code Playgroud)

现在,以下指令是否违反了严格的别名规则?

int bar = *foo;
Run Code Online (Sandbox Code Playgroud)

根据标准:

如果程序尝试通过以下类型之一以外的glvalue访问对象的存储值,则行为未定义:

  • 对象的动态类型,
  • 对象动态类型的cv限定版本,
  • 与对象的动态类型类似(定义见4.4)的类型,
  • 类型是与对象的动态类型相对应的有符号或无符号类型,
  • 一种类型,是与对象的动态类型的CV限定版本相对应的有符号或无符号类型,
  • 集合或联合类型,在其元素或非静态数据成员(递归包括子集合或包含的联合的元素或非静态数据成员)中包括上述类型之一,
  • 该类型是对象动态类型的(可能是cv限定的)基类类型,
  • 字符或无符号字符类型。

map / foo指向的对象的动态类型是什么?那甚至是物体吗?该标准说:

类型T对象的生命周期在以下情况下开始:获得具有类型T正确的对齐方式和大小的存储,并且如果对象具有非平凡的初始化,则其初始化完成。

这是否意味着映射的内存包含10个int对象(假设初始地址已对齐)?但是,如果这是真的,那么这是否也不适用于此代码(这显然会破坏严格的别名)?

char baz[sizeof(int)];
int* p=reinterpret_cast<int*>(&baz);
*p=5;
Run Code Online (Sandbox Code Playgroud)

甚至奇怪的是,这是否意味着声明baz开始了大小为4的任何对象(正确对齐)的生存期?


一些情况:我正在映射一个文件,其中包含我希望直接访问的大量数据。由于此块很大,因此我想避免存储到临时对象。


*在这里可以将nullptr代替NULL,是否将其隐式转换为NULL?该标准有什么参考吗?

Yak*_*ont 5

我相信简单的转换确实违反了严格的别名。认为令人信服地高于我的薪水,因此这是一种解决方法:

template<class T>
T* launder_raw_pod_at( void* ptr ) {
  static_assert( std::is_pod<T>::value, "this only works with plain old data" );
  char buff[sizeof(T)];
  std::memcpy( buff, ptr, sizeof(T) );
  T* r = ::new(ptr) T;
  std::memcpy( ptr, buff, sizeof(T) );
  return r;
}
Run Code Online (Sandbox Code Playgroud)

我相信上面的代码对内存的可观察到的副作用为零,并返回一个指向合法T*位置的指针ptr

检查您的编译器是否将上述代码优化为noop。为此,它必须真正了解 memcpy基础知识,而构造a T则对那里的内存没有任何作用。

至少clang 4.0.0可以优化此操作

我们要做的是首先将字节复制。然后我们使用new放置来创建一个Tthere。最后,我们将字节复制回去。

我们已经合法地创建T了我们想要的字节。

但是副本的复制和复制都将复制到本地缓冲区,因此它没有明显的效果。

对象的构造(如果是容器)也不必接触字节。从技术上讲,字节是未定义的。但是聪明的编译器会说“什么也不做”。

因此,编译器可以确定可以在运行时跳过所有这些操作。同时,我们在抽象机中正确创建了一个在该位置具有适当字节的对象。(假设它具有有效的对齐方式!但这不是此代码的问题。)

  • 仅供参考:自4.6起的所有gcc版本也对此进行了优化。 (2认同)
  • 很整洁,但就我而言,我不允许写入 ptr 指向的内存。如果代码得到优化可能没问题,但如果没有优化就会带来麻烦。 (2认同)