8

我在不违反 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!
  // ...
}


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

谢谢

4

3 回答 3

3

实际上,您所写的并不是严格的混叠违规。

C++11 规范 3.10.10 说:

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

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

当然,您不打算在设置 smem[0] 之前对其进行设置。你要先写信给它。分配给同一个内存不会访问存储的值,因此没有别名 但是,在对象还活着的时候在对象的顶部进行写入是非法的。为了证明我们是安全的,我们需要从 3.8.4 开始的对象生命周期:

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

你有一个 POD 类型,所以是微不足道的析构函数,所以你可以简单地口头声明“int 对象都处于其生命周期的尽头,我正在使用浮动空间。” 然后,您将空间重新用于浮点数,并且不会发生别名冲突。

于 2013-09-05T06:41:52.330 回答
1

将对象解释为字节序列始终是有效的(即,将任何对象指针视为指向字符数组的第一个元素的指针并不违反别名),并且您可以在任何片段中构造对象足够大且适当对齐的内存。

因此,您可以分配一个大数组chars (任何符号),并找到一个对齐的偏移量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();
}

请注意,由mallocor获得new char[N]的内存始终对齐为最大对齐(但不是更多,您可能希望使用过度对齐的地址)。

于 2013-09-04T22:19:04.503 回答
0

如果联合用于保存 int 和 float 变量,则可以通过严格别名。http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html中提供了更多相关信息

另请参阅以下文章。

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

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

于 2013-09-04T22:32:36.587 回答