簡體   English   中英

C 聯合型雙關語 arrays

[英]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);
}

我的觀察:

  • 假設 arguments 到func之間不互為別名, func不違反嚴格別名。
  • 在 C 中,允許使用union進行類型雙關。
  • m16m32傳遞給func一定會違反某些規定。

我的問題:

  • 像這樣用 arrays 雙關語有效嗎?
  • 我將指針傳遞給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.m16mine.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM