指针别名规则的应用(指向自身的指针)

时间:2014-09-26 20:54:43

标签: c++ restrict restrict-qualifier

我最近遇到了一个讨厌的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)并不认为自我混淆是可能的。这是允许的吗?

2 个答案:

答案 0 :(得分: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做出假设。

答案 1 :(得分:0)

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

  

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

  

嗯,有一件事立即浮现在脑海中。这是在64位平台上运行吗?如果是这样,则指针算术中的计算不考虑填充。

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

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

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

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

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