2017-01-18 39 views
0

我有許多結構,其具有第一3個字段常見的,下面是 簡化的示例:通用API來初始化結構與共用字段

struct my_struct1 { 
    /* common fields. */ 
    int a; 
    int b; 
    int c; 
}; 

struct my_struct2 { 
    int a; 
    int b; 
    int c; 
    uint32_t d; 
    uint32_t e; 
}; 

struct my_struct3 { 
    int a; 
    int b; 
    int c; 
    uint16_t d; 
    char e; 
}; 

static void func1(struct my_struct1 *s) 
{ 
    /* ... */ 
} 

static void func2(struct my_struct2 *s) 
{ 
    /* ... */ 
} 

static void func3(struct my_struct3 *s) 
{ 
    /* ... */ 
} 

int main(void) 
{ 
    struct my_struct1 s = {1, 2, 3}; 
    struct my_struct2 p = {1, 2, 3, 4, 5}; 
    struct my_struct3 q = {1, 2, 3, 4, 'a'}; 

    func1(&s); 
    func2(&p); 
    func3(&q); 

    /* XXX */ 
    func3((struct my_struct3 *)&s); 

    return 0; 
} 

它是安全的類型轉換sstruct my_struct3 *並傳遞給func3並確保s或堆棧上分配的其他對象不會被破壞?

原因是我想編寫一個通用API,它需要一個指針,初始化通用字段(這是通用的結構)。另一個功能是特定於my_struct*並設置其餘的字段。

我不確定void *是否可以解決這個問題。

UPDATE 我應該指出,很遺憾,我不能改變的結構佈局,即增加公共部分是不是一種選擇,因爲我正在使用的代碼是很老,我不允許改變其核心結構。

唯一的解決辦法醜,我看到的是通過void *enum struct_type參數generic_init功能,以及基於struct_typevoid *適當的結構。

+3

不,這是*不允許的。但是,爲什麼不把通用部分放入一個結構中,然後將其用作其他結構的第一個成員?然後,您可以將指針轉換爲其中一個派生結構的指針,以指向基礎結構。 – EOF

+2

只需將「常用字段」移動到另一個子結構中並使其成爲其他字段。然後將該字段傳遞給初始化函數 –

回答

2

據我可以解釋標準,鑄造my_struct1*類型的指針mystruct_3*類型或副蘆薈的指針可能由於指針轉換規則收率未定義的行爲(參見C11 standard ISO/IEC 9899:TC2):

6.3.2.3指針...(7)一個指向對象或不完整的類型可被轉換成一個指針到一個不同的對象或不完整的類型。如果 生成的指針未針對指向的 類型正確對齊,則行爲未定義。 ...

因此,如my_struct1my_struct3可具有不同的取向,根據my_struct1不一定根據my_struct3正確對齊被正確對齊的指針。

但是,即使你能保證所有的結構具有相同的排列,指針傳遞到my_struct1類型的對象,以func3是 - 在我看來 - 並不安全,即使對普通會員是每首當其衝結構,即使func3只訪問常用成員。 原因是,一個編譯器可能引入構件之間填充:

6.7.2.1結構和聯合說明符(13)內的結構對象,非位字段構件和單元,其中位地址爲 的地址的地址增加的次數是 。指向結構對象的指針,經過適當轉換後,指向 到其初始成員(或者如果該成員是位字段,則指向其所在的 單元),反之亦然。 在結構對象中可能存在未命名的 填充,但不在其開始處。

因此,如my_struct1my_struct3具有不同組的成員,一個編譯器如何引入填充的規則可以這兩個結構之間變化。我認爲這種情況不太可能發生,但是我沒有發現標準中的任何聲明保證填充 - 即使是前三名成員 - 對於my_struct1my_struct3也是如此。

0

它是半安全的。如果第一個成員相同,則可以保證指向結構的指針也是指向第一個成員的指針。從技術上講,編譯器可以在第一個成員之後插入任意填充。事實上,沒有編譯器會這樣做,所以如果兩個結構共享第一個和第二個成員,指向第二個成員的指針也具有相同的偏移量。但是,您的成員「b」的偏移量可能不是address + sizeof(int)。整數可能填充到8個字節的性能。

爲避免歧義,您可以明確地將通用成員設置爲結構「common」。

+0

您的答案在技術上是錯誤的,但如果問題是否可以安全地將'my_struct_n *'轉換爲'my_struct_1 *',那麼這個問題實際上是有爭議的。但是,問題是否可以轉換爲'my_struct_3 *',它包含'my_struct_1'不包含的成員,因此即使對齊可能也是錯誤的。 (考慮一下'sizeof(int)== 2'和'sizeof(uint32)== 4')的機器。 – EOF

2

爲了充實EOF和Eugene Sh的評論。已經解釋了:

將my_struct1強制轉換爲my_struct3並不安全,因爲my_struct3具有更多成員,my_struct1和編譯器根本不會警告訪問這些其他成員(d和e),覆蓋任何後面的成員my_struct1。只要my_struct1完全對應於my_struct3的開始,反過來也可以。我不確定在標準中是否有任何擔保會覆蓋你,但我不會賭。

在一個單獨的結構類型 分離出的共同部分的優點如下:

  • 這減少在代碼具有的 的優點,允許你改變在一個地方的共同碼重複,減少了錯誤的風險。
  • 編譯器可以檢查傳入的類型,通過 轉換結構可以有效地禁用這些檢查。
  • 通過將結構體作爲結構體成員,編譯器可以爲你找出正確的偏移量,就不需要在結構體的起始處使用公共結構體。

struct common { 
    int a; 
    int b; 
    int c; 
}; 

struct my_struct1 { 
    struct common com; 
}; 

struct my_struct2 { 
    struct common com; 
    uint32_t d; 
    uint32_t e; 
}; 

struct my_struct3 { 
    struct common com; 
    uint16_t d; 
    char e; 
}; 

void init_common(struct common *com) 
{ 
    com->a = 1; 
    com->b = 2; 
    /* ... */ 
} 

struct my_struct1 s = {{1, 2, 3}}; 
struct my_struct2 p = {{1, 2, 3}, 4, 5}; 
struct my_struct3 q = {{1, 2, 3}, 4, 'a'}; 

init_common(&s.com); 
init_common(&p.com); 
init_common(&q.com); 
+0

感謝您的評論,我將在未來的工作中利用這種方法,但現在不可能,請參閱我的更新後的文章。 – Mark