C ++中的共享内存缓冲区,不违反严格的别名规则

时间:2013-09-04 21:55:18

标签: c++ memory buffer strict-aliasing type-punning

我正在努力实现共享内存缓冲区而不破坏C99严格的别名规则。

假设我有一些处理一些数据的代码,需要有一些“临时”内存来运行。我可以把它写成:

void foo(... some arguments here ...) {
  int* scratchMem = new int[1000];   // Allocate.
  // Do stuff...
  delete[] scratchMem;  // Free.
}

然后我有另一个函数来做一些其他需要临时缓冲的东西:

void bar(...arguments...) {
  float* scratchMem = new float[1000];   // Allocate.
  // Do other stuff...
  delete[] scratchMem;  // Free.
}

问题是foo()和bar()可能在操作期间被多次调用,并且就性能和内存碎片而言,遍布整个地方的堆分配可能非常糟糕。一个明显的解决方案是分配一个适当大小的公共共享内存缓冲区,然后将其作为参数传递给foo()和bar(),BYOB样式:

void foo(void* scratchMem);
void bar(void* scratchMem);

int main() {
  const int iAmBigEnough = 5000;
  int* scratchMem = new int[iAmBigEnough];

  foo(scratchMem);
  bar(scratchMem);

  delete[] scratchMem;
  return 0;
}

void foo(void* scratchMem) {
  int* smem = (int*)scratchMem;
  // Dereferencing smem will break strict-aliasing rules!
  // ...
}

void bar(void* scratchMem) {
  float* smem = (float*)scratchMem;
  // Dereferencing smem will break strict-aliasing rules!
  // ...
}


我想我现在有两个问题:
- 如何实现不违反别名规则的共享公共暂存内存缓冲区? - 即使上面的代码确实违反了严格的别名规则,别名也没有“伤害”。因此,任何理智的编译器都可以生成(优化的)代码,但仍会让我遇到麻烦吗?

由于

3 个答案:

答案 0 :(得分:3)

实际上,你所写的并不是严格的别名冲突。

C ++ 11规范3.10.10说:

  

如果程序试图通过除了其中一个之外的glvalue访问对象的存储值   以下类型行为未定义

因此导致未定义行为的是访问存储的值,而不仅仅是创建指向它的指针。你的例子没有违反任何规定。它需要做下一步:浮动badValue = smem [0]。 smem [0]从共享缓冲区中获取存储的值,从而产生别名冲突。

当然,在设置之前,你不会只是抓住smem [0]。你要先写信给它。分配给相同的内存不会访问存储的值,因此没有任何关联但是,在对象仍处于活动状态时写入对象的顶部是非法的。为了证明我们是安全的,我们需要3.8.4的对象寿命:

  

程序可以通过重用对象占用的存储空间来结束任何对象的生命周期   使用非平凡的析构函数显式调用析构函数以获取类类型的对象。对于一个对象   对于具有非平凡析构函数的类类型,程序不需要显式调用析构函数   在重新使用或释放​​对象占用的存储之前; ...... [继续关于不调用析构函数的后果]

你有一个POD类型,所以很简单的析构函数,所以你可以简单地声明“int对象都在他们的生命周期结束时,我正在使用浮动空间。”然后,您可以将该空间重用于浮点数,并且不会发生别名冲突。

答案 1 :(得分:1)

将一个对象解释为一个字节序列总是有效的(即一个别名冲突将任何对象指针视为指向chars数组的第一个元素的指针),你可以在任何一块足够大且适当对齐的记忆中构造一个物体。

因此,您可以分配大量char个s(任何签名),并找到alignof(maxalign_t)处的偏移量;现在,您可以在那里构建适当的对象后将该指针解释为对象指针(例如,在C ++中使用placement-new)。

你当然必须确保不要写入现有对象的记忆;事实上,对象的生命周期与代表对象的内存发生的情况密切相关。

示例:

char buf[50000];

int main()
{
    uintptr_t n = reinterpret_cast<uintptr_t>(buf);
    uintptr_t e = reinterpret_cast<uintptr_t>(buf + sizeof buf);

    while (n % alignof(maxalign_t) != 0) { ++n; }

    assert(e > n + sizeof(T));

    T * p = :: new (reinterpret_cast<void*>(n)) T(1, false, 'x');

    // ...

    p->~T();
}

请注意,mallocnew char[N]获取的内存总是对齐以进行最大对齐(但不是更多,您可能希望使用过度对齐的地址)。< / p>

答案 2 :(得分:0)

如果使用union来保存int和float变量,那么可以通过严格别名来传递。有关这方面的更多信息请参阅 http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html

另见以下文章。

http://blog.regehr.org/archives/959

他提供了一种使用工会来做到这一点的方法。