【问题标题】:Application of Pointer Aliasing Rule (Pointer to Addess of Itself)指针别名规则的应用(指向自身地址的指针)
【发布时间】:2014-09-26 20:54:43
【问题描述】:

我最近遇到了一个讨厌的 schrödinbug。在尝试将文件加载到平面内存表示中时,作者编写了如下代码:

class Line final { public:
    int stuff[3];
    char* data;
}

//...

Line* line = /*...*/;
//Trying to treat line->data like an array.  This is *wrong*.
line->data = reinterpret_cast<char*>(line) + 3*sizeof(int);

//...

line->data[0] = /*...*/
line->data[1] = /*...*/
//...
line->data[n] = /*...*/ //"line->data" changes because of this line!

所以,发生的事情是第一行代码基本上将line-&gt;data 设置为等于&amp;line-&gt;data。这是一个错误,因为对line-&gt;data 指向的值的任何更改也可能会更改line-&gt;data 本身指向的内容!

我当时很奇怪,问题发生的时间太长了。我的理解是,除非使用 restrict(或 g++/MSVC __restrict)限定,否则编译器必须假定指针是别名的。因此,如果我将line-&gt;data[0] 设置为某个东西,那么它将对下一次访问line-&gt;data[1] 可见,并且几乎肯定会无效。然而,在调试器中,更改直到很久以后才可见,并且写入愉快地继续了一段时间。

我猜编译器(在本例中为 MSVC 2013)不认为自别名是可能的。允许吗?

【问题讨论】:

  • 你的问题基本上是这样的:“为什么line-&gt;data[1] = ...没有崩溃?”?
  • 是的。在计算line-&gt;data[1] 时,line-&gt;data[0] 的写入应该对line-&gt;data 的后续负载可见——除非别名不包括自锯齿。
  • 可能是因为填充,取决于系统的位数。
  • 嗯,马上想到的一件事就是对齐。这是在 64 位平台上运行的吗?如果是这样,则指针算术中的计算没有考虑填充。
  • @OliverCharlesworth @T.C.这确实是在 64 位平台上。你能澄清一下填充是如何应用的吗? sizeof(Line)==24,所以我猜在data 之前有一个4 字节填充——这使得line-&gt;data 稍微 &amp;line-&gt;data 之前。有趣的是,虽然变化经常发生在4 附近,但有时可能会更高。

标签: c++ restrict restrict-qualifier


【解决方案1】:

我的理解是,除非使用restrict(或g++/MSVC __restrict)限定,否则编译器必须假定指针是别名的。

这是不正确的。允许编译器假定指针仅对指向相同类型的指针或指向char 的指针进行别名。

class X;
class Y;
X *ptr_x = ...;
Y *ptr_y = ...;
char *ptr_char = ...;

在这里,编译器可以假设ptr_x 没有别名ptr_y。但是,它不能对ptr_char 做出假设。

【讨论】:

  • 上次我检查了 MSVC 不支持严格别名,所以我假设它必须假设所有指针都是别名。无论如何,这里所讨论的指针一个char指针。
  • @IanMallett:确实。 可能这是一个编译器错误。但是,必须首先从程序中删除未定义的行为。
【解决方案2】:

很难确切知道问题出在哪里。我早就解决了这个问题,现在还有几个项目。回想起来,原来问题上的 cmets 似乎最成功地提供了解释行为的线索:

可能是因为填充,取决于系统的位数。

和:

嗯,马上想到的一件事就是对齐。这是在 64 位平台上运行的吗?如果是这样,则指针算法中的计算没有考虑填充。

在 64 位架构上,这确实是在编译的,我猜原始问题中的类会像这样布置在内存中(为清晰起见调整了类型):

int32_t stuff_0;
int32_t stuff_1;
int32_t stuff_2;
//4 bytes of empty space
char* data;

发生填充是因为char* 指针需要与8-byte 对齐。由于前三个ints 占用3*32/8=96/8=12 字节,为了获得该对齐,编译器需要插入一个额外的4 字节以将开销带到一轮16 字节。

data 初始化时,它被错误地初始化为指向空白空间的开头。所以,写信给data[n]0&lt;=n&lt;4 打填充。只有在访问data[4] 时才会遇到问题。

我说“最成功”,因为虽然问题大部分发生在第五次访问前后,但在我的记忆中,问题有时会在以后发生,即使在调试时也是如此。而且,正如我所写的,这一个 schrödinbug(也就是说,一个应该发生但没有发生的错误——现在它已经被观察到,总是如此)。我没有关于正在运行的前一种数据的数据,但可能是逻辑导致关键指针范围不受影响。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-30
    • 2014-07-26
    • 2018-04-05
    • 2016-02-14
    相关资源
    最近更新 更多