一. 概述
經常會遇到計算結構體偏移量的需求, 比如有下面這樣一個結構體:
struct mav_protocol { char header; char seq; short command_id; char payload[256]; int crc32; } p;
需要在傳輸到對端前填入它的crc32值,以確保對端在收到這組數據后能夠根據填入的crc32值判斷收到的這組數據是否仍然正確。
那一般都會有一個公共的函數去計算結構體里某段數據的crc值,函數原型大概如下:
int get_crc32(char *buf, int size) { // 計算CRC... return 0; }
需要傳入一個數據指針以及需要計算數據的偏移量,對於我們這個例子來說,需要傳入的數據指針就是 struct mav_protocol * , 偏移量就是結構體里crc32這個成員之前的所有數據的長度,也就是 header, seq, command_id...., 等等數據需要參與crc計算。一般計算偏移量的做法是 :
int offset = (unsigned char *)&p.crc32 - (unsigned char *)&p;
然后再代入上面的那個計算crc32的函數:
get_crc32((char *)&p, offset);
今天發現了另一種更巧妙的寫法:
二. 具體寫法
我看到這哥們直接定義了一個宏:
#define offset(type, v) (&(((type *)0)->v))
然后在使用時直接使用 offset(struct mav_protocol, crc32) 來拿到 crc32這個結構體成員在結構中的偏移量,剛開始很疑惑這種寫法,后來反復看了幾次之后明白了其中的妙處:
簡單的說,既然結構體成員的地址減去結構體的地址就等於該成員的偏移量,那如果結構體的地址為0,該成員的地址就恰好等於它在結構體中的偏移量了。
下面做一個實驗來驗證這個寫法是否正確:
#include <stdio.h> #include <string.h> #define offset(type, v) (&(((type *)0)->v)) int get_crc32(char *buf, int size); struct mav_protocol { char header; char seq; short command_id; char payload[256]; int crc32; } p; int main() { printf("struct p's address is: 0x%x\n", &p); printf("header field's address id: 0x%x\n", &p.header); printf("---------------------------------\n"); printf("crc32 field's address: 0x%x\n", &p.crc32); int offset = (unsigned char *)&p.crc32 - (unsigned char *)&p; printf("offset is: %d\n", offset); printf("-----------------------------------------------------------\n"); printf("crc32 offset: %d\n", offset(struct mav_protocol, crc32)); return 0; }
運行:
struct p's address is: 0x407040
header field's address id: 0x407040
---------------------------------
crc32 field's address: 0x407144
offset is: 260
-----------------------------------------------------------
crc32 offset: 260
可以看到,不管是使用普通的做法還是使用宏的做法得到的結果都是一致的,這樣以后需要計算任意結構體成員的偏移量都可以通過這個宏,只傳一個結構名和一個結構體成員名就可以了。
而且,無論以后如何調整這個結構體的成員,刪除也好,新增也罷,只要保證crc32是它的最后一個成員,計算校驗值的代碼就無需改動,這樣的C語言代碼維護起來也是非常省心的。