1. 程式人生 > >C結構體和offset巨集

C結構體和offset巨集

[Mac-10.7.1  Lion  Intel-based  x64  gcc4.2.1]

Q: 結構體的本質是什麼?

A: 結構體就像一種粘合劑,將事物之間的關係很好地組合在了一起。

Q: 結構體物件中各個變數的記憶體儲存位置和內建基本型別變數的儲存有什麼區別?

A: 簡單地說,它們沒區別;複雜地說,它們有區別。簡單在於它們終究會儲存在記憶體中,複雜地說它們的位置可能有一些不同。如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
  4. typedefstruct
  5. {  
  6.     char    sex;  
  7.     int     age;  
  8. }student;  
  9. // print every byte of the obj
  10. void    print_struct(void   *obj, int size)  
  11. {  
  12.     int i;  
  13.     unsigned char *temp = (unsigned char *)obj;  
  14.     for (i = 0; i < size; ++i)  
  15.     {  
  16.         printf("%#x ", temp[i]);  
  17.     }  
  18.     printf("\n");  
  19. }  
  20. int main()  
  21. {  
  22.     student s;  
  23.     s.sex = 'm';  
  24.     s.age = 25;  
  25.     print_struct(&s, sizeof(student));  
  26.     return 0;  
  27. }  
執行結果:

可以看到s物件的sex成員值為'm',對應於0x6d, s的age成員值對應於0x19.因為筆者的機器為小端法,所以它們都在低地址顯示,高地址補零。這裡也可以看到student結構中的sex成員雖然是char型別,卻依然佔用了4位元組,這是被補齊了。

Q: 如何獲取結構體中一個成員的偏移位置呢?

A: 

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
  4. typedefstruct
  5. {  
  6.     char    sex;  
  7.     int     age;  
  8. }student;  
  9. int main()  
  10. {  
  11.     student s;  
  12.     PRINT_D((char *)&s.sex - (char *)&s)  
  13.     PRINT_D((char *)&s.age - (char *)&s)  
  14.     return 0;  
  15. }  

如上程式碼,定義一個student型別的物件s,輸出s的sex成員的地址和s地址的差即為偏移,同理age的偏移也可以獲得。

Q: 有更一般的方法麼,不用建立物件,直接獲得一個結構體某個成員的偏移位置?

A: 有的。更一般就意味著更抽象。將上面的過程抽象,取一個特定結構體物件的某個成員的偏移。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
  4. typedefstruct
  5. {  
  6.     char    sex;  
  7.     int     age;  
  8. }student;  
  9. int main()  
  10. {  
  11.     PRINT_D(&((student *)0)->sex - (char *)0)  
  12.     PRINT_D((char *)&((student *)0)->age - (char *)0)  
  13.     return 0;  
  14. }  
上面的程式碼,在地址0抽象出了一個student型別的指標,然後獲得各個成員的偏移;

執行結果:

Q: 上面的格式看起來有點複雜,有更簡單的封裝麼?

A: 我們可以採用巨集定義:

  1. #define OFFSET(struct, member)  ((char *)&((struct *)0)->member - (char *)0)
如下程式碼:
  1. #include <stdio.h>
  2. #include <string.h>
  3. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));
  4. #define OFFSET(struct, member)  ((char *)&((struct *)0)->member - (char *)0)
  5. typedefstruct
  6. {  
  7.     char    sex;  
  8.     int     age;  
  9. }student;  
  10. int main()  
  11. {  
  12.     PRINT_D(OFFSET(student, sex))  
  13.     PRINT_D(OFFSET(student, age))  
  14.     return 0;  
  15. }  
輸出結果:

當然,也可以使用系統標頭檔案中定義的巨集offsetof, 示例程式碼如下:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stddef.h>
  4. #define PRINT_LUD(intValue)     printf(#intValue" is %lu\n", (intValue));
  5. #define OFFSET(struct, member)  ((char *)&((struct *)0)->member - (char *)0)
  6. typedefstruct
  7. {  
  8.     char    sex;  
  9.     int     age;  
  10. }student;  
  11. int main()  
  12. {  
  13.     PRINT_LUD(offsetof(student, sex))  
  14.     PRINT_LUD(offsetof(student, age))  
  15.     return 0;  
  16. }  

Q: 上面程式碼中的OFFSET巨集,用地址0處的指標訪問成員,難道不會出現訪問違例?

A: 先寫如下程式碼:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
  4. #define OFFSET(struct, member)  ((char *)&((struct *)0)->member - (char *)0)
  5. typedefstruct
  6. {  
  7.     char    sex;  
  8.     int     age;  
  9. }student;  
  10. int main()  
  11. {  
  12.     student *s = (student *)0;  
  13.     PRINT_D(s->age)  
  14.     return 0;  
  15. }  
程式碼試圖訪問地址0處的資料,執行:

可以看到出現訪問違例。那麼OFFSET巨集是如何正確執行的呢?我們檢視下使用OFFSET的程式碼的彙編:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define PRINT_D(intValue)     printf(#intValue" is %d\n", (intValue));
  4. #define OFFSET(struct, member)  ((char *)&((struct *)0)->member - (char *)0)
  5. typedefstruct
  6. {  
  7.     char    sex;  
  8.     int     age;  
  9. }student;  
  10. int main