[英]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>
從地址中可以看到, v
和second
在堆棧上, 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.