0

我最近遇到了一个讨厌的 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->data为等于&line->data. 这是一个错误,因为对 所指向的值的任何更改line->data也可能会更改其line->data本身所指向的内容!

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

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

4

2 回答 2

0

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

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

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

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

于 2014-09-26T21:02:26.060 回答
0

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

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

和:

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

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

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

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

初始化时data,它被错误地初始化为指向空白空间的开头。因此,写入data[n]0<=n<4点击填充。只有在访问data[4]时我们才会遇到问题。

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

于 2014-10-01T04:12:10.330 回答