2011-10-26 37 views
8

thể trùng lặp:
Why does this C code work?
How do you use offsetof() on a struct?Macro bù đắp C hoạt động như thế nào?

tôi đọc về vấn đề này offsetof vĩ mô trên Internet, nhưng nó không giải thích những gì nó được sử dụng cho.

#define offsetof(a,b) ((int)(&(((a*)(0))->b))) 

Nó đang cố gắng làm gì và lợi thế của việc sử dụng nó là gì?

+2

Đó 'offsetof' vĩ mô là không chính xác. Họ nên đúc thành 'size_t', không phải' int', và chúng có lẽ nên trừ '(char *) 0' khỏi kết quả trước khi truyền mặc dù nó là hằng số con trỏ null. –

Trả lời

12

Không có lợi thế và không nên sử dụng vì nó gọi hành vi không xác định (và sử dụng sai loại - int thay vì size_t).

Tiêu chuẩn C định nghĩa một offsetof vĩ mô trong stddef.h mà thực sự hoạt động đối với trường hợp bạn cần bù đắp của một phần tử trong một cấu trúc, chẳng hạn như:

#include <stddef.h> 

struct foo { 
    int a; 
    int b; 
    char *c; 
}; 

struct struct_desc { 
    const char *name; 
    int type; 
    size_t off; 
}; 

static const struct struct_desc foo_desc[] = { 
    { "a", INT, offsetof(struct foo, a) }, 
    { "b", INT, offsetof(struct foo, b) }, 
    { "c", CHARPTR, offsetof(struct foo, c) }, 
}; 

mà sẽ cho phép bạn lập trình điền vào các lĩnh vực a struct foo theo tên, ví dụ khi đọc một tệp JSON.

+0

Tôi xin lỗi - làm thế nào để bù đắp macro gây ra hành vi không xác định đặc biệt là kể từ khi nó được xác định trong tiêu chuẩn C? –

+3

Macro 'offsetof' chuẩn từ' stddef.h' không gọi UB. Xác định hack của riêng bạn để tính toán offsets theo cách này không gọi UB. –

+0

Vui lòng báo cho tôi tham chiếu chuẩn xác định phiên bản macro của riêng bạn gây ra hành vi không xác định –

4

Đó là việc tìm bù đắp byte của một thành viên cụ thể là struct. Ví dụ, nếu bạn có cấu trúc sau:

struct MyStruct 
{ 
    double d; 
    int i; 
    void *p; 
}; 

Sau đó, bạn sẽ phải offsetOf(MyStruct, d) == 0, offsetOf(MyStruct, i) == 8, và offsetOf(MyStruct, p) == 12 (có nghĩa là, các thành viên mang tên d lượng là 0 byte từ khi bắt đầu của cấu trúc, vv).

Cách hoạt động là giả vờ rằng một thể hiện cấu trúc của bạn tồn tại tại địa chỉ 0 (phần ((a*)(0))), và sau đó nó lấy địa chỉ của thành viên cấu trúc dự định và đặt nó vào một số nguyên. Mặc dù dereferencing một đối tượng tại địa chỉ 0 thường sẽ là một lỗi, nó là ok để có địa chỉ vì địa chỉ của nhà điều hành & và thành viên dereference -> hủy bỏ lẫn nhau.

Nó thường được sử dụng cho các khung công tác tuần tự hóa tổng quát. Nếu bạn có mã để chuyển đổi giữa một số loại dữ liệu dây (ví dụ: byte trong một tệp hoặc từ mạng) và cấu trúc dữ liệu trong bộ nhớ, thường là thuận tiện để tạo ánh xạ từ tên thành viên đến thành viên bù đắp, để bạn có thể tuần tự hóa hoặc deserialize giá trị một cách chung chung.

+0

Câu hỏi là C, chúng ta vẫn phải sử dụng 'struct MyStruct'. ;) –

24

R .. là chính xác trong câu trả lời của mình cho phần thứ hai của câu hỏi của bạn: mã này không được thông báo khi sử dụng trình biên dịch C hiện đại.

Nhưng để trả lời phần đầu của câu hỏi của bạn, điều này là thực sự làm là:

(
    (int)(  // 4. 
    &((  // 3. 
     (a*)(0) // 1. 
    )->b)  // 2. 
) 
) 

Làm việc từ trong ra ngoài, đây là ...

  1. Đúc giá trị zero với loại con trỏ struct a*
  2. Bắt lĩnh vực struct b này (đặt bất hợp pháp) đối tượng struct
  3. Lấy địa chỉ của b lĩnh vực này
  4. Đúc địa chỉ để một int

Khái niệm này đặt đối tượng struct ở địa chỉ bộ nhớ 0 và sau đó tìm ra địa chỉ của một phân tử trường ar là. Điều này có thể cho phép bạn tìm ra các offset trong bộ nhớ của từng trường trong một cấu trúc, do đó bạn có thể viết các trình tuần tự hóa riêng và các trình tuần tự hóa của bạn để chuyển đổi các cấu trúc đến và từ các mảng byte. Tất nhiên nếu bạn thực sự không quan tâm đến một con trỏ bằng không chương trình của bạn sẽ sụp đổ, nhưng thực sự tất cả mọi thứ xảy ra trong trình biên dịch và không có con trỏ không thực tế nào bị hủy đăng ký trong thời gian chạy.

Trong hầu hết các hệ thống ban đầu mà C chạy trên kích thước của int là 32 bit và giống như con trỏ, vì vậy điều này thực sự hiệu quả.

-2

Việc triển khai macro offsetof thực sự không liên quan.

Tiêu chuẩn C thực tế định nghĩa nó như trong 7.17.3:

offsetof(type, member-designator) 

mà mở rộng để một biểu thức hằng số nguyên mà có kiểu size_t, giá trị trong số đó là bù đắp bằng byte, để các thành viên kết cấu (được chỉ định bởi thành viên thiết kế), từ đầu cấu trúc của nó (được chỉ định theo loại). Kiểu chỉ định và thành viên sẽ được gán cho số static type t;.

Tin tưởng câu trả lời của Adam Rosenfield.

R hoàn toàn sai và có nhiều công dụng - đặc biệt là có thể biết khi nào mã không thể di chuyển giữa các nền tảng.

(OK, đó là C++, nhưng chúng tôi sử dụng nó trong mẫu tĩnh biên dịch khẳng định thời gian để đảm bảo cấu trúc dữ liệu của chúng tôi không thay đổi kích thước giữa các nền tảng/phiên bản).

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