2013-12-23 61 views
4

我不確定代碼是否在asserts強制轉換中存在指針別名(或其他標準一致性問題)。看來,指向聯合類型的指針應該能夠轉換爲第一個成員的指針,並且因爲聯合只由這兩個結構組成,所以我認爲對第一個成員的轉換應該可行,但我不是確定如果這是正確的,或者如果我在這個過程中填充細節的細節。工會是否需要填充高位?與普通第一成員結合的結構體

看起來這是不明確的行爲?有沒有人有任何洞察力,這是否是支持的。我知道有利用結構與enum type場和struct container_storage成員這樣做的另一種標準的方式,但它似乎是浪費空間考慮,這個信息已經在struct contained

編譯命令在Linux中:gcc -std=c99 -Wextra -pedantic -fstrict-aliasing test.c && ./a.out && echo $?返回0

#include <stdlib.h> 
#include <assert.h> 

enum type {type_a = 1, type_b = 2}; 

struct contained { 
    int some_other_field; 
    enum type type; 
}; 

struct container_a { 
    struct contained contained; 
    int test; 
}; 


struct container_b { 
    struct contained contained; 
    char test; 
}; 

union container_storage { 
    struct container_a container_a; 
    struct container_b container_b; 
}; 

int 
main(int argc, char **argv) 
{ 
    union container_storage a = 
     {.container_a = {.contained = {.type = type_a}, .test = 42}}; 
    union container_storage b = 
     {.container_b = {.contained = {.type = type_b}, .test = 'b'}}; 

    assert(((struct contained *)&a)->type == type_a); 
    assert(((struct contained *)&b)->type == type_b); 

    return EXIT_SUCCESS; 
} 

參考文獻:

[1] gcc, strict-aliasing, and casting through a union

[2] What is the strict aliasing rule?

回答

2

這應該沒問題。 C11,6.5.2.3/6(「結構和工會會員」)說:如果一個聯合包含 共享一個公共初始序列幾種結構:

一個特殊的保障是爲了簡化使用工會製成(見下文),並且如果工會對象當前包含這些結構中的一個,則允許檢查其中任何一個的公共 的初始部分,其中可以看到工會 的完成類型的聲明。如果對應的成員 對於一個或多個初始成員的序列具有兼容的類型(並且對於位域,具有相同的寬度),則兩個結構共享共同的初始序列

(C++使得同樣的保證(C++ 11,9.2/18),用於標準佈局聯合。)

+0

很高興聽到。我試圖挖掘C99規範,但我似乎無法在任何地方找到它。謝謝! – backscattered

2

union不要墊,他們只是覆蓋他們的成員。任何struct的第一個成員保證立即開始,沒有填充。一般而言,struct以相同類型的相同成員開頭,保證與該初始部分具有相同的佈局。

1

在C89,結構類型的指針識別一個聯合的成員可以是用於檢查作爲與存儲在其中的數據類型共享的共同初始序列的一部分的任何成員。這反過來一般意味着可以使用指向任何結構類型的指針來檢查與任何其他類型共享的公共初始序列的任何成員(如果該對象碰巧是聲明的聯合對象的成員,則這種行爲將被明確定義,編譯器在這些情況下產生所需行爲的唯一可行方法是爲所有人維護它)。

C99增加了一個額外的要求,即CIS保證僅適用於包含兩種結構的聯合類型可見的情況,一些編譯器編寫者似乎認爲它僅適用於通過聯合類型直接執行的訪問。這樣的編譯器的作者似乎認爲將需要一個共同的頭部像處理功能的功能:

struct smallThing { void *next; uint16_t length; uint8_t dat[2]; }; 
struct bigThing { void *next; uint16_t length; uint8_t dat[65528]; }; 

應該是把解壓出來的標題,如:

struct uHeader { void *next; uint16_t length; }; 
struct smallThing { uHeader head; uint8_t dat[2]; }; 
struct bigThing { uHeader head; uint8_t dat[15994]; }; 

或使用聯合型儘管使用uHeader將會使struct smallThing的大小增加50%(並且完全破壞任何代碼, 依賴於它的佈局),並且在大多數對象只需要很小的情況下對所有東西使用聯合會增加內存使用千倍。

如果需要代碼與基本上忽略公共初始序列規則的編譯器兼容,則應該將公共初始序列規則視爲基本無用。就我個人而言,我認爲最好記錄一下,只有那些尊重CIS的編譯器應該被認爲適合與代碼一起使用,而不是爲了適應不適合的編譯器而向後退避開,但我認爲重要的是要知道像後者這樣的編譯器有一些存在。

據我所知,clang和gcc不以任何有用的方式兌現CIS規則,除非設置了-fno-strict-aliasing標誌。我不知道其他編譯器。

+0

這是一個很好的答案。我認爲編寫一個不合規的編譯器列表將是一個好主意。 – backscattered

+1

@backscattered:我不知道有多少編譯器在決定強制遵守Common Initial Sequence保證的唯一方法時應該禁用所有基於類型的優化,但恕我直言,保證的目的是明確,堅持它的成本應遠遠低於對所有指針訪問進行悲觀推定的成本。 – supercat