2014-09-20 15 views
5

Giả sử thư viện C phải chia sẻ chi tiết về cấu trúc với mã ứng dụng và phải duy trì tính tương thích ngược của API và ABI. Nó cố gắng làm điều này bằng cách kiểm tra kích thước của cấu trúc truyền cho nó.Làm thế nào để sizeof (struct) giúp cung cấp khả năng tương thích ABI?

Giả sử, cần phải cập nhật cấu trúc sau. Trong phiên bản thư viện 1,

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

trong phiên bản 2 của thư viện, nó được cập nhật đến:

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

Bây giờ, thư viện phiên bản 2 muốn kiểm tra nếu ứng dụng được đi qua các mới foo_2 hoặc cũ foo làm đối số, arg, với một hàm. Nó giả định rằng các ứng dụng đã cài đặt arg.size-sizeof(foo) hoặc sizeof(foo_2) và cố gắng tìm hiểu xem mã ứng dụng groks phiên bản 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. 
} 

Tôi đang tự hỏi tại sao điều này sẽ không thất bại. Trên GCC 4.6.3, với cờ -O3, cả hai sizeof(foo)sizeof(foo_2) là 24. Vì vậy, mã thư viện v2 sẽ không hiểu nếu ứng dụng đang chuyển cấu trúc loại foo hoặc foo_2? Nếu có, cách tiếp cận này dường như đã được sử dụng như thế nào?

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


theo về câu hỏi: Có một lý do chính đáng để ủng hộ việc sử dụng sizeof(struct) cho phiên bản phân biệt đối xử? Như đã chỉ ra trong các nhận xét, tại sao không sử dụng thành viên version rõ ràng trong cấu trúc được chia sẻ?

+1

Bạn nhận được 24 từ đâu? –

+0

Điều này có thể không hoạt động. 'sizeof' là một điều biên dịch, và bạn muốn kiểm tra kích thước tại * runtime *. –

+0

@BasileStarynkevitch: Hm, cái gì? Chúng tôi wat để biết phiên bản của cấu trúc người gọi được sử dụng, không phải là callee, do đó, nó có vẻ tốt từ hướng đó. Tuy nhiên, điều này là đúng trên hầu hết các nền tảng 64 bit, con trỏ là 8 byte liên kết và có kích thước, int 4, và do đó không có sự khác biệt về kích thước giữa hai cấu trúc. – Deduplicator

Trả lời

2

Để phù hợp với các quan sát của bạn, tôi thừa nhận

  • char* có kích thước 8 và sự liên kết 8.
  • int có kích thước 4 và alignment 4.
  • thực hiện của bạn sử dụng bao bì tối ưu.

Bạn hoàn toàn đúng trong trường hợp đó, cả cấu trúc cũ và mới của bạn sẽ có cùng kích thước và phân biệt phiên bản của bạn là kích thước cấu trúc, nâng cấp là thay đổi ABI. (Rất ít lỗi logic cũng là lỗi cú pháp và lỗi trước đây không được trình biên dịch chẩn đoán).

Chỉ thay đổi cấu trúc dẫn đến kích thước lớn hơn, với cấu trúc mới chứa tất cả các trường của trường cũ ở cùng vị trí, có thể tương thích ABI trong lược đồ đó: Thêm một số biến giả.


Có một khả năng mà có thể tiết kiệm trong ngày mặc dù:

  • Nếu một lĩnh vực chứa một giá trị mà trước đây không hợp lệ, mà có thể chỉ ra rằng bất cứ điều gì khác có thể phải được giải thích differencty.
+2

giải pháp thích hợp là pad v2 của cấu trúc sao cho nó không có cùng kích thước 'sizeof' – Mgetz

1

Tôi đề xuất sử dụng cấu trúc trung gian. Ví dụ:

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; 

Trong phiên bản thư viện của tôi 2, tôi sẽ xuất khẩu theo tên các chức năng sau:

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); 
} 

Sau đó, trong ứng dụng chính tôi sẽ làm:

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); 
} 

Như thường lệ, kiểm tra bảo mật (khi cấp phát bộ nhớ chẳng hạn) không được bao gồm để có thể đọc được mã.

Ngoài ra, tôi sẽ sử dụng stdint.h để đảm bảo các kiểu dữ liệu tương thích nhị phân (để chắc chắn các kích thước của int, double, char* và vân vân đều giống nhau trên kiến ​​trúc khác nhau). Ví dụ: thay vì int tôi sẽ sử dụng int32_t.

+0

APR? Chỉ cần sử dụng stdint.h – James

+0

@ James Cảm ơn, chỉ cần chỉnh sửa câu trả lời của tôi :) – HRold

1

Nếu bạn muốn sử dụng lược đồ này để phân biệt các phiên bản API khác nhau, bạn chỉ cần đảm bảo rằng các phiên bản cấu trúc khác nhau có kích thước khác nhau.

Để làm như vậy, bạn có thể cố gắng làm cho foo nhỏ hơn bằng cách buộc trình biên dịch sử dụng đóng gói chặt hơn hoặc bạn có thể làm cho các trường bổ sung (không sử dụng) thêm foo_2 lớn hơn.

Trong bất kỳ cách nào, bạn nên thêm xác nhận (tốt nhất là lúc biên dịch) cho sizeof(foo) != sizeof(foo_2) để đảm bảo cấu trúc luôn thực sự có kích thước khác nhau.

Các vấn đề liên quan