[英]C union type punning arrays
鑒於以下代碼,我有一些與類型雙關相關的問題。 我看不出有任何方式表明這沒有違反嚴格的別名規則,但我無法指出具體的違規行為。 我最好的猜測是將工會成員傳遞到 function 違反了嚴格的別名。
以下代碼在Compiler Explorer上。
#include <stdint.h>
union my_type
{
uint8_t m8[8];
uint16_t m16[4];
uint32_t m32[2];
uint64_t m64;
};
int func(uint16_t *x, uint32_t *y)
{
return *y += *x;
}
int main(int argc, char *argv[])
{
union my_type mine = {.m64 = 1234567890};
return func(mine.m16, mine.m32);
}
我的觀察:
func
之間不互為別名, func
不違反嚴格別名。union
進行類型雙關。m16
和m32
傳遞給func
一定會違反某些規定。我的問題:
func
究竟違反了什么?違反的規則是 C 2018 6.5.16.1 3:
如果存儲在 object 中的值是從另一個 object 中讀取的,它以任何方式與第一個 object 的存儲重疊,那么重疊應該是准確的,並且兩個對象應該具有兼容類型的合格或不合格版本; 否則,行為未定義。
具體來說,在*y += *x
中,存儲在y
指向的 object 中的值mine.m16
是從另一個 object mine.m32
中讀取的,它與mine.m16
的存儲重疊,但重疊不是exact 並且無論限定符如何,對象都沒有兼容的類型。
請注意,此規則適用於簡單分配,如E1 = E2
,而代碼具有組件分配, E1 += E2
。 但是,復合賦值E1 += E2
在 6.5.16.2 3 中定義為等效於E1 = E1 + E2
,只是左值E1
僅被評估一次。
像這樣用 arrays 雙關語有效嗎?
是的,C 標准允許通過工會成員使用別名; 讀取除最后一個存儲的成員之外的成員將重新解釋新類型中的字節。 但是,如果程序的行為由 C 標准(尤其是上面引用的規則)定義,這並不能免除程序遵守其他規則的責任。
將指針傳遞給 func 到底違反了什么?
傳遞指針不違反任何規則。 如上所述,使用指針的分配違反了規則。
在這個例子中我還遺漏了哪些問題?
如果我們改變func
:
int func(uint16_t *x, uint32_t *y)
{
*y += 1;
*x += 1;
return *y;
}
那么 6.5.16.1 3 中的規則不適用,因為沒有涉及重疊對象的賦值。 並且沒有違反 6.5 7 中的別名規則,因為*y
是定義為用於訪問它的類型uint16_t
的 object,而*x
是定義為用於訪問它的類型uint32_t
的 object。 然而,如果這個 function 是單獨翻譯的( union
定義不可見),允許編譯器假定*x
和*y
不重疊,因此它可以緩存*y += 1;
*y
值; 並在不知道*x += 1;
改變*y
。 這是 C 標准中的缺陷。
將 m16 和 m32 傳遞給 func 一定會違反某些規定。
func(uint16_t *x, uint32_t *y)
可以自由假設*x
和*y
不重疊,因為x, y
是足夠不同的指針類型。 由於引用的數據確實在 OP 的代碼中重疊,因此我們遇到了問題。
關於union
s 和別名的特殊問題不適用於func()
的主體,因為調用代碼的聯合性丟失了。
替代的“安全”代碼可能是:
// Use volatile to prevent folding these 2 lines of code.
// The key is that even with optimized code,
// the sum must be done before *y assignment.
volatile uint32_t sum = *y + *x;
*y = sum;
return (int) (*y);
將指針傳遞給 func 到底違反了什么?
將指針傳遞給 function func()
無需考慮的重疊數據。
像這樣用 arrays 雙關語有效嗎?
我不認為這是一個數組或union
問題,只是將指針傳遞給 function func()
沒有義務考慮的重疊數據之一。
在這個例子中我還遺漏了哪些問題?
輕微: int
可能是 16 位,可能導致在將uint32_t
轉換為int
時實現定義的行為。
考慮兩者之間的區別
uint32_t fun1(uint32_t *a, uint32_t *b);
uint32_t fun2(uint32_t * restrict a, uint32_t * restrict b);
fun1()
必須考慮重疊的可能性。 fun2()
不會。
我的觀察:
- 假設 arguments 到
func
之間不互為別名,func
不違反嚴格別名。
不可靠。 所謂的嚴格別名規則是根據用於訪問給定 object 的左值表示的,相對於該對象的有效類型。 func()
的兩個 arguments 不需要相互別名來執行func()
以產生嚴格別名違規。 例子:
uint32_t x = 0, y = 1;
func((uint16_t *)&x, &y);
// func will violate strict aliasing when it dereferences its first parameter
圍繞 function 參數相互別名的問題將是 realm 的restrict
限定指針,您沒有使用它。
- 在 C 中,允許使用 union 進行類型雙關。
是的,前提是雙關語是通過聯合object 執行的。這包含在C17 6.5/7中,即上述嚴格別名規則:
object 的存儲值只能由具有以下類型之一的左值表達式訪問:
[...]
- 在其成員中包含上述類型之一的聚合或聯合類型
請注意,這與正在訪問的存儲實際上位於聯合 object 內無關,而是關於用於訪問它的左值類型相對於正在訪問的 object 的實際(有效)類型。
- 將 m16 和 m32 傳遞給 func 一定會違反某些規定。
它確實如此,盡管語言規范可能比現在更清楚。 但是,它確實說:
任何時候最多可以將其中一個成員的值存儲在一個聯合 object 中。
( C17 6.7.2.1/16 )
在您的特定示例中, mine.m16和mine.m16
都沒有在調用時存儲在mine.m32
的值,但在任何情況下,至多它們中的一個可能有值。 當func
然后嘗試讀取存儲在這些對象中的值時,結果未定義(因為它們實際上沒有存儲值)。
第 6.5.2.3/6 段規范中包含的內容支持該解釋:
為了簡化聯合的使用,做出了一個特殊的保證:如果一個聯合包含多個共享公共初始序列的結構(見下文),並且如果聯合 object 當前包含這些結構之一,則允許檢查公共它們中任何一個的初始部分,在聯合的完整類型聲明可見的任何地方。
如果訪問隨機聯合成員通常是可以的,而不管哪個成員實際存儲了值,那么就不需要這樣的特殊規定。
我的問題:
- 像這樣用 arrays 雙關語有效嗎?
不是那樣的,不。 規范還允許數組類型雙關的其他變體。
- 將指針傳遞給 func 到底違反了什么?
調用本身不違反任何內容。 獲取聯合成員的地址是合法的,即使當前沒有存儲值的成員也是如此,將結果指針值傳遞給函數也是合法的。 但是當用那些 arguments 調用時, function在它試圖取消引用一個或兩個指針時會犯嚴格別名違規,如上所述。
- 在這個例子中我還遺漏了哪些問題?
與您的其他答案之一相反,所提供的代碼與第 6.5.16.1/3 段沒有沖突。 存儲在*y
中的值不是從重疊的 object *x
中讀取的,而是該值與*y
的原始值的總和。 該總和是計算得出的,而不是從 object 中讀取的,因此6.5.16.1/3
不適用。 但是您可能忽略了如果func()
執行簡單賦值而不是加號會違反6.5.16.1/3
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.