C語言:變長結構體
一.概述
在Linux程式中,經常會看到形如下面的結構體定義
struct xfrm_algo { char alg_name[64]; unsigned int alg_key_len; /* in bits */ char alg_key[0]; };
這裡,最奇怪的是結構體最後一個元素, 是一個零長度的字元陣列
這裡先解釋一下結構體的作用。
xfrm_algo是一個定義金鑰演算法的結構體,alg_name存放演算法名稱,alg_key_len存放金鑰長度(單位是bit),alg_key存放金鑰. 因為同一個演算法,有可能會使用不同長度的金鑰。
如AES, 就有128位、192位和256位三種金鑰。 所以,在定義這樣一個金鑰演算法的結構體時,就要求不定長的結構體,而零長陣列就可實現這一點。
當然,我們也可以使用指標來代替
struct xfrm_algo { char alg_name[64]; unsigned int alg_key_len; /* in bits */ char * alg_key; };
下面,分別用指標和零長陣列實現不定長結構體。
方法1:定義一個xfrm_algo結構體變數,再為alg_key成員動態建立記憶體
這種情況下,實際的xfrm_algo結構體和金鑰是分離的
#include<stdio.h> #include<stdlib.h> #include<string.h> voidprint_hex( unsigned char *buf, int len); struct xfrm_algo { char alg_name[64]; unsigned int alg_key_len; unsigned char * alg_key; }; int main( void ) { char alg[] = "AES"; unsigned char key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, / 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; struct xfrm_algo algo1; memcpy( algo1.alg_name, alg, sizeof(alg) ); algo1.alg_key_len = sizeof(key) * 8; if( ( algo1.alg_key = (unsigned char *)malloc( sizeof(key) ) ) == NULL ) { perror("malloc"); return -1; } memcpy( algo1.alg_key, key, sizeof(key) ); printf("sizeof(struct xfrm_algo) = %d/n", sizeof(struct xfrm_algo)); printf("algo1: 0x%08x/n", &algo1); printf("/talg_name : 0x%08x(%s)/n", algo1.alg_name, algo1.alg_name); printf("/talg_key_len: 0x%08x(%d)/n", &algo1.alg_key_len, algo1.alg_key_len); printf("/talg_key : 0x%08x", algo1.alg_key); print_hex( algo1.alg_key, sizeof(key) ); free(algo1.alg_key); return 0; } void print_hex( unsigned char *buf, int len) { int i = 0; printf("("); for( i = 0; i < len; i++ ) { printf("0x%02x ", buf[i]); } printf(")/n"); }
執行結果:
$./struct_pointer1
sizeof(struct xfrm_algo) = 72
algo1: 0xbff54108
alg_name : 0xbff54108(AES)
alg_key_len: 0xbff54148(128)
alg_key : 0x09b2f008(0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f )
從輸出可觀察到, alg_key_len與alg_key是分離的
方法2: 直接為xfrm_algo和已知的金鑰動態建立記憶體
此時,xfrm_algo結構體和金鑰是連續的。
#include<stdio.h> #include<stdlib.h> #include<string.h> void print_hex( unsigned char *buf, int len); struct xfrm_algo { char alg_name[64]; unsigned int alg_key_len; unsigned char * alg_key; }; int main( void ) { char alg[] = "AES"; unsigned char key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, / 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; struct xfrm_algo *palgo = NULL; if( ( palgo = (struct xfrm_algo * )malloc( sizeof(struct xfrm_algo) + sizeof(key) ) ) == NULL ) { perror("malloc"); return -1; } memcpy( palgo->alg_name, alg, sizeof(alg) ); palgo->alg_key_len = sizeof(key) * 8; palgo->alg_key = (unsigned char *)( palgo + 1 ); memcpy( palgo->alg_key, key, sizeof(key) ); printf("sizeof(struct xfrm_algo) = %d/n", sizeof(struct xfrm_algo)); printf("palgo: 0x%08x/n", palgo); printf("/talg_name : 0x%08x(%s)/n", palgo->alg_name, palgo->alg_name); printf("/talg_key_len: 0x%08x(%d)/n", &palgo->alg_key_len, palgo->alg_key_len); printf("/talg_key : 0x%08x", palgo->alg_key); print_hex( palgo->alg_key, sizeof(key) ); free(palgo); return 0; } void print_hex( unsigned char *buf, int len) { int i = 0; printf("("); for( i = 0; i < len; i++ ) { printf("0x%02x ", buf[i]); } printf(")/n"); }
執行結果:
$ ./struct_pointer2
sizeof(struct xfrm_algo) = 72
palgo: 0x096bd008
alg_name : 0x096bd008(AES)
alg_key_len: 0x096bd048(128)
alg_key : 0x096bd050(0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f )
從輸出可觀察到, alg_key_len與alg_key是連續的。這裡,alg_key似乎是多餘的,因為我們總能使用(unsigned char *)( palgo + 1 )得到key的首地址。
方法3:零長度陣列
在標準C語言中,是不允許零長度陣列的。但 GNU C 允許。
#include<stdio.h> #include<stdlib.h> #include<string.h> void print_hex( unsigned char *buf, int len); struct xfrm_algo { char alg_name[64]; unsigned int alg_key_len; unsigned char alg_key[0]; }; int main( void ) { char alg[] = "AES"; unsigned char key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, / 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; struct xfrm_algo *palgo = NULL; if( ( palgo = (struct xfrm_algo *)malloc( sizeof(struct xfrm_algo) + sizeof(key) ) ) == NULL ) { perror("malloc"); return -1; } memcpy( palgo->alg_name, alg, sizeof(alg) ); palgo->alg_key_len = sizeof(key) * 8; memcpy( palgo->alg_key, key, sizeof(key) ); printf("sizeof(struct xfrm_algo) = %d/n", sizeof(struct xfrm_algo)); printf("palgo: 0x%08x/n", palgo); printf("/talg_name : 0x%08x(%s)/n", palgo->alg_name, palgo->alg_name); printf("/talg_key_len: 0x%08x(%d)/n", &(palgo->alg_key_len), palgo->alg_key_len); printf("/talg_key : 0x%08x", palgo->alg_key); print_hex( palgo->alg_key, palgo->alg_key_len / 8 ); free(palgo); return 0; } void print_hex( unsigned char *buf, int len) { int i = 0; printf("("); for( i = 0; i < len; i++ ) { printf("0x%02x ", buf[i]); } printf(")/n"); }
執行結果:
$./struct_array
sizeof(struct xfrm_algo) = 68
palgo: 0x0980d008
alg_name : 0x0980d008(AES)
alg_key_len: 0x0980d048(128)
alg_key : 0x0980d04c(0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f )
xfrm_algo結構體大小為68個位元組, alg_key[0]不佔儲存空間,和方法1、2相比,少了個unsigned char *指標,但可移植性不比前兩種方法。
二.這種寫法的優勢
結構體最後使用0或1的長度陣列的原因,主要是為了方便的管理記憶體緩衝區,如果你直接使用指標而不使用陣列,那麼,你在分配記憶體緩衝區時,就必須分配結構體一次,然後再分配結構體內的指標一次,(而此時分配的記憶體已經與結構體的記憶體不連續了,所以要分別管理即申請和釋放)。
而如果使用陣列,那麼只需要一次就可以全部分配出來,反過來,釋放時也是一樣,使用陣列,一次釋放,使用指標,得先釋放結構體內的指標,再釋放結構體。還不能顛倒次序。
其實變長結構體就是分配一段連續的的記憶體,減少記憶體的碎片化,簡化記憶體的管理。
三.應用場景
<1>Socket通訊資料包的傳輸;
<2>解析資料包,如筆者遇到的問題。
<3>其他可以節省空間,連續儲存的地方等。
參考連結: