我最近遇到了一个讨厌的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)并不认为自我混淆是可能的。这是允许的吗?
答案 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(也就是说,一个应该已经发生但却没有的错误 - 现在它已被观察到,总是如此)。我没有关于上一类数据运行的数据,但可能逻辑可能导致关键指针范围不受影响。