mmap和C ++严格别名规则

时间:2017-07-25 18:14:27

标签: c++ c++11 posix

考虑符合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;

根据标准:

  

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

     
      
  • 对象的动态类型,
  •   
  • 对象的动态类型的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的对象的生命周期?

某些上下文:我正在编写一个文件,其中包含我希望直接访问的一大块数据。由于这个块很大,我想避免记忆临时对象。

*这里可以nullptr而不是NULL,是否隐式转换为NULL?标准的任何参考?

1 个答案:

答案 0 :(得分: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;
}

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

检查编译器是否将上述代码优化为noop。要做到这一点,它必须在一个非常基础的层面上理解 memcpy,并且构建T必须对那里的内存不做任何事。

At least clang 4.0.0 can optimize this operation away

我们所做的是首先复制远的字节。然后我们使用新的展示位置在那里创建T 。最后,我们将字节复制回来。

我们有一个合法创建的T,其中包含我们想要的字节。

但是副本和背面都是本地缓冲区,因此它没有可观察到的效果。

如果一个pod,对象的构造也不必触及字节;从技术上讲,字节是未定义的。但聪明的编译器说“无所事事”。

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