我認爲在技術上這個例子也是UB。但它幾乎肯定會奏效,gcc和clang都不會用-pedantic
來抱怨。
首先,下面是在C99(§ 6.5.2.3/6)明確定義的:[1]
union OneTwo {
struct One one;
struct Two two;
};
OneTwo tmp = {.two = {3, {'a', 'b'}, 3.14f, 3.14159} };
One one = tmp.one;
在訪問 「punned」 struct One
通過union
必須工作意味着事實上struct Two
前綴的佈局與struct One
相同。這不能取決於是否存在union
,因爲給定的複合類型只能有一個存儲佈局,其佈局不能取決於其在union
中的使用情況,因爲union
不需要對每個翻譯單元都可見使用struct
。此外,在C中,所有類型不超過一個字節序列(例如,與C++
不同)(§ 6.2.6.1/4)[2]。因此,下面的內容也是保證工作:
struct One one;
struct Two two = ...;
unsigned char tmp[sizeof one];
memcpy(tmp, two, sizeof one);
memcpy(one, tmp, sizeof one);
鑑於上述情況和任何指針類型的一個void*
可兌換,我認爲這是合理的結論是上述臨時存儲是不必要的,它可能有直接寫爲:通過別名指針在OP
struct One one;
struct Two two = ...;
unsigned char tmp[sizeof one];
memcpy(one, two, sizeof one);
從那裏直接分配至於是不是一個非常大的飛躍,但對於別名指向另外一個問題:它在理論上是可能的指針轉換來創建一個無效指針,因爲它可能是bi t格式的struct Two*
不同於struct One*
。儘管將一個指針類型轉換爲寬鬆對齊的另一個指針類型(§ 6.3.2.3/7)[3]然後再次轉換它是合法的,但不保證轉換的指針實際可用,除非轉換是到一個字符類型。具體而言,struct Two
的比對有可能與struct One
的比對不同(更嚴格),並且更強對齊的指針的比特格式不能直接用作指向不太強對齊的結構的指針。然而,很難看到與幾乎相同的論點:
one = *(struct One*)(void*)&two;
雖然這可能沒有明確保證的標準。
在評論中,不同的人提出了別名優化的幽靈。上面的討論根本不涉及別名,因爲我認爲這與簡單的賦值無關。該任務必須在任何前面的表達之後並在任何後續表達之前排序;它清楚地修改了one
並且幾乎如同清楚地引用two
。高度懷疑優化使得two
之前的法律變異對作業不可見。
但是通常情況下可以使用別名優化。因此,即使所有上述指針轉換在單個賦值表達式的上下文中都應該是可接受的,當然而不是是合法的行爲來保留類型爲struct One*
的轉換的指針,該指針實際上指向struct Two
類型的對象並期望它可用於改變其目標成員或訪問其目標的成員,否則該成員會發生變異。使用指向struct One
的指針就好像它是指向struct Two
前綴的指針的唯一上下文是當兩個對象重疊在union
中時。
---標準的引用:
[1]「,如果一個聯合包含共享公共初始序列幾種結構(見下文),並且如果聯合對象當前包含這些結構中的一個,它被允許檢查他們任何一個共同的初始部分,任何地方都可以看到完整的工會類型的聲明。「
[2]「存儲在任何其他對象類型的非位字段對象中的值由n×CHAR_BIT 位組成,其中n是該類型對象的大小(以字節爲單位),可以複製該值到 類型爲unsigned char [n]的對象(例如,通過memcpy)…「 [3]「指向對象類型的指針可能會轉換爲指向不同對象類型的指針;當指向對象的指針轉換爲指向字符類型的指針時,結果指向最低尋址的字節連續遞增的結果,直到對象的大小,產生指向對象剩餘字節的指針。「
我認爲這是將兩個變量的前三個變成一個,但我沒有測試它。無論如何,如果這是你想要的,分別移動每個變量。這是「好方法」。 – Abend
我不想完成一項任務,我想更好地理解有關該語言的晦澀難懂的細節。 – Subsentient
我強烈建議把'language-lawyer'標籤放在這個標籤上。這是你想要達到的觀衆。 – keshlam