繁体   English   中英

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

[英]GCC vs Clang copying struct flexible array member

考虑以下代码片段。

#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);
}

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

$ gcc-7 -o tmp tmp.c && ./tmp
abcd
abcd

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

$ clang -o tmp tmp.c && ./tmp
abcd
# Nothing here

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

编辑

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

second._ = 1;

但这不会改变输出。

这是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);
}

在具有gcc的32位Linux计算机上,得到以下输出:

0xbf89a303 0x804a020 0xbf89a2fc
<defgh> <abcdefgh> <abcdefgh>

从地址中可以看到, vsecond在堆栈上, first在数据部分中。 此外,还很明显, second的初始化已覆盖了堆栈上的v ,结果是代替了预期的<xxxxxxxx> ,而显示了<defgh>

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

编辑:我对此进行了更多测试,并通过将second的声明拆分为以下内容而获得了基本等效的结果:

s second;
second = first;

真正的问题是分配。 它复制的是first全部 ,而不是结构类型的最小公用部分,这是我认为应该做的。 实际上,如果将first的静态初始化移动到一个单独的文件中,则分配将执行应做的工作, v正确打印, second.str是未定义的垃圾。 这是gcc应该产生的行为,无论first的初始化在同一编译单元中是否可见。

因此,对于一个答案,两个编译器的行为均正确,但是您得到的答案是未定义的行为。

海湾合作委员会
因为您从不修改second GCC,所以只是在其查找表中将second和别名作为first 修改秒,GCC无法进行优化,您将获得与Clang相同的答案/崩溃。


似乎Clang不会自动应用相同的优化。 因此,当它复制结构时,它可以正确地执行此操作:它复制单个int ,而不复制其他任何内容。

您很幸运在本地second变量之后的堆栈上有一个零值,终止了未知的字符串。 基本上,您使用的是未初始化的指针。 如果没有零,那么您可能会得到很多垃圾和内存错误。

这件事的目的是通过将一些内存投射到您的结构上来做一些底层的工作 ,例如实现内存管理器等。 编译器没有义务了解您在做什么。 只有当您知道自己在做什么时才有义务采取行动。 如果您无法将结构类型强制转换到实际具有该类型数据的内存上,则所有选择均无效。

编辑
因此,使用godbolt.org并查看程序集:

.LC0:
        .string "%s\n%s\n"
main:
        sub     rsp, 24
        mov     eax, DWORD PTR first[rip]
        mov     esi, OFFSET FLAT:first+4
        lea     rdx, [rsp+16]
        mov     edi, OFFSET FLAT:.LC0
        mov     DWORD PTR [rsp+12], eax
        xor     eax, eax
        call    printf
        xor     eax, eax
        add     rsp, 24
        ret
first:
        .long   0
        .string "abcd"

我们看到,实际上,GCC确实按照我对OP的原始代码所说的去做:将second作为first的别名。

汤姆·卡兹(Tom Karzes)已对代码进行了重大修改,因此遇到了另一个问题。 他报告的内容似乎是一个错误; 我还没来得及让ATM弄清楚他的堆栈损坏任务到底发生了什么。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM