一. 概述
在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> 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 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>其他可以節省空間,連續存儲的地方等。
參考鏈接:
1. 變長結構體的表示方法
2. 深入淺出變長結構體