在this Stackoverflow question或文章Undefined behavior can result in time travel (among other things, but time travel is the funkiest)中,可以了解到访问大于其大小的索引的数据结构是未定义的行为,并且当编译器看到时未定义的行为,它生成疯狂的代码,甚至没有报告遇到未定义的行为。这样做是为了使代码运行速度快几纳秒,因为标准允许它。出现所谓的“时间旅行”,因为编译器在控制流分支上运行,当它在分支中看到未定义的行为时,它只删除该分支(基于任何行为将在未定义行为的地方)。
然而,这是一个古老的习语:
struct myString {
int length;
char text[1];
}
用作
char* s = "hello, world";
int len = strlen(s);
myString* m = malloc(sizeof(myString) + len);
m->length = len;
strcpy(&m->text,s);
现在,如果我访问m->text[3]
会怎样? (注意它是如何声明的。)编译器会将其视为未定义的行为吗?如果是,我如何将一组静态未知数量的项目添加到结构的末尾?
特别是,我对一个大小至少为1但可能更多的数组感兴趣。排序,
struct x {
unsigned u[1];
};
and access like `struct x* p; ... p->x[3]`.
UPD相关:Is the "struct hack" technically undefined behavior?(正如@mafso在评论中指出的那样)。
答案 0 :(得分:2)
这可能是一个古老的习语,但两者都是未定义的行为 C和C ++。从C99开始,您可以编写如下内容:
struct MyString
{
int length;
char text[];
};
并按照您的描述使用它(尽管您可能需要
在malloc
中将长度加1。在C ++中,你需要跳转
通过几个箍:
struct MyString
{
int length
char* text()
{
return reinterpret_cast<char*>( this + 1 );
}
};
但是,对于char
以外的任何其他内容,您需要观看
出于对齐限制,因为编译器不知道
结构的结尾必须正确对齐
如下。 (G ++使用,或者至少使用过这样的东西
它的实施std::basic_string
。和实例化
像std::basic_string<double>
这样的机器会崩溃
size_t
只有4个字节,并且需要访问双精度数
8字节对齐。)
答案 1 :(得分:1)
为了回答你的问题,编译器没有对数组索引进行边界检查,这就是灵活数组工作的原因。您可以在任何数组中使用任何索引。
答案 2 :(得分:0)
你似乎对“未定义行为”的含义有一个奇怪的想法。编译器不需要识别导致未定义行为的代码,如果这样做,则不需要对它们做任何特别的事情。事实上,一种非常合理的方法是根本不做任何特殊处理。 undefined不是关于编译器可能生成的机器代码,而是关于运行它的效果。
话虽如此,数组和指针之间存在微妙但非常重要的区别。数组(例如char text[1]
)具有关联存储,而指针(char *text
或char text[]
)则没有。
如果我访问m-&gt; text [3]会发生什么? (注意它是如何声明的。)编译器会将其视为未定义的行为吗?
嗯,第一个未定义的行为与之前的语句相关联:
strcpy(&m->text,s);
m-&gt; text指的是长度为1的char数组,strcpy()
将在其末尾写入。就定义而言,在为结构分配的块中保留额外空间的事实是无关紧要的。在实践中,可能做你想要的,但依赖它是一个可怕的想法。
类似适用于访问m->text[3]
。编译器更有可能识别越界访问,但即使这样做,效果也可能是你想要的。 “可能”,但不是某些。这就是为什么要避免依赖未定义的行为。