2014-09-20 62 views
5

假設C庫必須與應用程序代碼共享結構的細節,並且必須保持API和ABI向後兼容性。它試圖通過檢查傳遞給它的結構的大小來做到這一點。sizeof(struct)如何幫助提供ABI兼容性?

說,以下結構需要更新。在庫版本1,

typedef struct { 
    int size; 
    char* x; 
    int y; 
} foo; 

在庫的版本2,將其更新爲:現在

typedef struct { 
    int size; 
    char* x; 
    int y; 
    int z; 
} foo_2; 

,庫版本2要檢查應用程序是否通過新foo_2或老foo作爲參數arg。它假定應用程序已設置arg.sizesizeof(foo)sizeof(foo_2),並嘗試找出應用程序代碼是否groks版本2

if(arg.size == sizeof(foo_2)) { 
    // The application groks version 2 of the library. So, arg.z is valid. 
} else { 
    // The application uses of version 1 of the library. arg.z is not valid. 
} 

我不知道爲什麼這不會失敗。在GCC 4.6.3,與-O3標誌,既sizeof(foo)sizeof(foo_2)是24.所以,不會V2庫中的代碼看不懂,如果應用程序正在通過foo型或foo_2的結構?如果是,那麼這種方法似乎是如何被使用的?

http://wezfurlong.org/blog/2006/dec/coding-for-coders-api-and-abi-considerations-in-an-evolving-code-base/

http://blogs.msdn.com/b/oldnewthing/archive/2003/12/12/56061.aspx


按照第一個問題:是否有一個很好的理由爲有利於區分版本使用sizeof(struct)?正如在評論中指出的,爲什麼不在共享結構中使用明確的version成員?

+1

你從哪裏得到24? – 2014-09-20 10:58:53

+0

這可能不起作用。 'sizeof'是一個編譯時的事情,你想檢查* runtime *的大小。 – 2014-09-20 11:03:11

+0

@BasileStarynkevitch:嗯,什麼?我們知道調用者使用哪個版本的結構,而不是被調用者,因此從這個方向看起來很好。儘管如此,棘手的是,在大多數64位平臺上,指針是8字節對齊和大小的,int 4,因此兩個結構之間沒有尺寸差異。 – Deduplicator 2014-09-20 11:06:34

回答

2

爲了搭配你的觀察,我斷定

  • char*有大小8和對齊8
  • int有大小4和對齊4.
  • 你實現使用最佳的包裝。

在這種情況下,您的新舊結構將具有相同的大小,並且由於版本鑑別器是結構大小,所以升級是ABI突破性更改。 (很少邏輯錯誤也是語法錯誤,而前者不是由編譯器診斷的)。

只改變結構,導致更大的尺寸,新的結構包含舊的一個相同的偏移量的所有字段,可以在該方案下與ABI兼容:添加一些虛擬變量。


有一個可能性,這可能會挽救雖然天:

  • 如果字段中包含這在以前是無效的值,這可能表明,什麼都可能被解釋differencty。
+2

正確的解決方案是填充結構的v2,所以它沒有相同的'sizeof' – Mgetz 2014-09-20 11:14:44

1

我建議使用中間結構。 例如:

typedef struct 
{ 
    int   version; 
    void*   data; 
} foo_interface; 

typedef struct 
{ 
    char*   x; 
    int   y; 
} foo; 

typedef struct 
{ 
    char*   x; 
    int   y; 
    int   z; 
} foo_2; 

在我的庫版本2,我想通過名稱導出以下功能:

foo_interface* getFooObject() 
{ 
    foo_interface* objectWrapper = malloc(sizeof(foo_interface)); 
    foo_2* realObject = malloc(sizeof(foo_2)); 

    /* Fill foo_2 with random data... */ 
    realObject.x = malloc(1 * sizeof(char)); 
    realObject.y = 2; 
    realObject.z = 3; 

    /* Fill our interface. */ 
    objectWrapper.version = 2; /* Here we specify version 2. */ 
    objectWrapper.data = (void*)realObject; 

    /* Return our wrapped data. */ 
    return (objectWrapper); 
} 

然後在主應用程序,我會做:

int main(int ac, char **av) 
{ 
    /* Load library + Retrieve getFooObject() function here. */ 

    foo_interface* objectWrapper = myLibrary.getFooObject(); 

    switch (objectWrapper->version) 
    { 
     case 1: 
      foo* realObject = (foo*)(objectWrapper ->data); 
      /* Do something with foo here. */ 
      break; 
     case 2: 
      foo_2* realObject = (foo_2*)(objectWrapper ->data); 
      /* Do something with foo_2 here. */ 
      break; 
     default: 
      printf("Unknown foo version!"); 
      break; 
    } 
    return (0); 
} 

像往常一樣,安全檢查(例如分配內存時)不包括在內爲了代碼的可讀性。

另外,我會用stdint.h,以確保數據類型的二進制兼容性(可以肯定的intdoublechar*等的大小在不同的體系結構相同)。例如,而不是int我會使用int32_t

+0

APR?只需使用stdint.h – James 2014-09-20 13:40:15

+0

@James謝謝,編輯我的答案:) – HRold 2014-09-20 16:31:35

1

如果您想使用此方案來區分API的不同版本,您只需確保不同的結構版本具有不同的大小。

爲此,您可以通過強制編譯器使用更緊密的打包來嘗試縮小foo,或者通過添加額外的(未使用的)字段來增大foo_2

無論如何,您應該爲sizeof(foo) != sizeof(foo_2)添加一個斷言(最好在編譯時),以確保結構總是具有不同的大小。