1. 程式人生 > 實用技巧 >C語言:變長結構體

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>

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.深入淺出變長結構體