GCC与Clang复制结构灵活数组成员

bna*_*ker 5 c gcc clang

考虑以下代码片段。

#include <stdio.h>

typedef struct s {
    int _;
    char str[];
} s;
s first = { 0, "abcd" };

int main(int argc, const char **argv) {
    s second = first;
    printf("%s\n%s\n", first.str, second.str);
}
Run Code Online (Sandbox Code Playgroud)

当我用GCC 7.2编译时,我得到:

$ gcc-7 -o tmp tmp.c && ./tmp
abcd
abcd
Run Code Online (Sandbox Code Playgroud)

但是,当我使用Clang(Apple LLVM版本8.0.0(clang-800.0.42.1))进行编译时,得到以下信息:

$ clang -o tmp tmp.c && ./tmp
abcd
# Nothing here
Run Code Online (Sandbox Code Playgroud)

为什么编译器之间的输出不同?我希望该字符串不会被复制,因为它是一个灵活的数组成员(类似于此问题)。为什么GCC实际上会复制它?

编辑

一些评论和答案表明这可能是由于优化。GCC可能second是的别名first,因此更新second应禁止GCC进行优化。我添加了一行:

second._ = 1;
Run Code Online (Sandbox Code Playgroud)

但这不会改变输出。

Tom*_*zes 4

这是 gcc 发生的事情的真正答案。 second正如您所期望的那样,在堆栈上分配。它不是 的别名first。通过打印他们的地址可以轻松验证这一点。

此外,该声明s second = first;会破坏堆栈,因为 (a) gcc 正在为其分配最小存储量,second但 (b) 它将所有内容复制first到第二个位置,从而破坏了堆栈。

这是原始代码的修改版本,显示了这一点:

#include <stdio.h>

typedef struct s {
    int _;
    char str[];
} s;
s first = { 0, "abcdefgh" };
int main(int argc, const char **argv) {
    char v[] = "xxxxxxxx";
    s second = first;
    printf("%p %p %p\n", (void *) v, (void *) &first, (void *) &second);
    printf("<%s> <%s> <%s>\n", v, first.str, second.str);
}
Run Code Online (Sandbox Code Playgroud)

在我的 32 位 Linux 机器上,使用 gcc,我得到以下输出:

0xbf89a303 0x804a020 0xbf89a2fc
<defgh> <abcdefgh> <abcdefgh>
Run Code Online (Sandbox Code Playgroud)

从地址可以看出,vsecond位于堆栈上,first位于数据部分。此外,还可以清楚地看出 的初始化second已被覆盖v,结果不是预期的<xxxxxxxx>,而是显示<defgh>

对我来说这似乎是一个 gcc 错误。至少,它应该警告初始化second将破坏堆栈,因为它显然有足够的信息在编译时知道这一点。

编辑:我对此进行了更多测试,并通过将声明拆分为获得了基本相同的结果second

s second;
second = first;
Run Code Online (Sandbox Code Playgroud)

真正的问题是任务。它复制所有first,而不是结构类型的最小公共部分,这是我认为它应该做的。事实上,如果将 的 静态初始化first移至单独的文件中,则该赋值会执行其应执行的操作,v正确打印,并且second.str是未定义的垃圾。这是 gcc 应该产生的行为,无论 的初始化是否first在同一编译单元中可见。