结构中的可变长度字符串和导致时间旅行的未定义行为

时间:2014-07-11 13:36:34

标签: c++ c gcc

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在评论中指出的那样)。

3 个答案:

答案 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 *textchar text[])则没有。

  

如果我访问m-&gt; text [3]会发生什么? (注意它是如何声明的。)编译器会将其视为未定义的行为吗?

嗯,第一个未定义的行为与之前的语句相关联:

strcpy(&m->text,s);

m-&gt; text指的是长度为1的char数组,strcpy()将在其末尾写入。就定义而言,在为结构分配的块中保留额外空间的事实是无关紧要的。在实践中,可能做你想要的,但依赖它是一个可怕的想法。

类似适用于访问m->text[3]。编译器更有可能识别越界访问,但即使这样做,效果也可能是你想要的。 “可能”,但不是某些。这就是为什么要避免依赖未定义的行为。