例解GNU C之零长数组与变长数组

http://blog.csdn.net/npy_lp/article/details/7009120


 前言:计算机语言是编译器和程序员交流的依据和规范,GNU C是GCC特有的功能,在Linux内核中被广泛应用。

    帮助文档:http://gcc.gnu.org/onlinedocs/gcc-4.6.2/gcc/C-Extensions.html#C-Extensions

 

    1、零长数组

    GNU C允许声明长度为零的数组,但它只能被用于结构体的最后一个成员。

    举例,如清单1: 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. struct line {  
  5.     int length;  
  6.     char contents[0];  
  7. };  
  8.   
  9. int main(void)  
  10. {  
  11.     int i, count = 9;  
  12.     char letter = 'A';  
  13.   
  14.     struct line *thisline = (struct line *)malloc(sizeof(struct line) + count);  
  15.       
  16.     thisline->length = count;  
  17.     for (i = 0; i < count; i++)  
  18.     thisline->contents[i] = letter++;  
  19.       
  20.     printf("sizeof(struct line) = %d\n"sizeof(struct line));  
  21.       
  22.     for (i = 0; i < thisline->length; i++)  
  23.     printf("%c ", thisline->contents[i]);  
  24.     printf("\n");  
  25.       
  26.     return 0;  
  27. }  

    例子输出结果: 

[cpp]  view plain copy
  1. sizeof(struct line) = 4  
  2. A B C D E F G H I   

    如例子中的第6行,contents就是一个零长数组,在sizeof看来它所占的空间为零。

    在ISO C99中,使用变长数组也可以实现同样的功能,如清单2: 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3.   
  4. struct line {  
  5.     int length;  
  6.     char contents[];  
  7. };  
  8.   
  9. struct line thisline = { 5, {'1''2''3''4''5' } };  
  10.   
  11. int main(void)  
  12. {  
  13.     int i;  
  14.       
  15.     printf("sizeof(struct line) = %d\n"sizeof(struct line));  
  16.     printf("sizeof(thisline) = %d\n"sizeof(thisline));  
  17.       
  18.     for (i = 0; i < thisline.length; i++)  
  19.     printf("%c ", thisline.contents[i]);  
  20.     printf("\n");  
  21.       
  22.     return 0;  
  23. }  

    例子输出结果: 

[cpp]  view plain copy
  1. sizeof(struct line) = 4  
  2. sizeof(thisline) = 4  
  3. 1 2 3 4 5   

    变长数组是不完全数据类型,不能使用sizeof获得它的大小。

    注意,此结构体的变量必须在函数外定义和初始化,否则会报错: 

[cpp]  view plain copy
  1. error: non-static initialization of a flexible array member  
  2. error: (near initialization for 'thisline')  

    不能使用这样的形式: 

[cpp]  view plain copy
  1. struct mystruct {  
  2.     int arr[];  
  3. };  

    否则会报错: 

[cpp]  view plain copy
  1. error: flexible array member in otherwise empty struct  

    2、变长数组

    在支持变长数组之前,C语言数组的大小是在声明时确定的(下标是一个常量表达式)并一直保持不变。所谓变长数组就是指数组的大小可以在运行时指定,如清单3: 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int main(void)  
  4. {  
  5.     int i;  
  6.   
  7.     scanf("%d", &i);  
  8.   
  9.     int arr[i];  
  10.   
  11.     printf("sizeof(arr[%d]) = %d\n", i, sizeof(arr));  
  12.   
  13.     return 0;  
  14. }  

    例子输出结果: 

[cpp]  view plain copy
  1. sizeof(arr[6]) = 24 //输入数字6  
  2.   
  3. sizeof(arr[9]) = 36 //输入数字9  

    输入不同的值,数组的大小随之改变。

    变长数组作为参数进行传递的例子,如清单4: 

[cpp]  view plain copy
  1. #include <stdio.h>  
  2.   
  3. int sum(int num, int arr[num])  
  4. {  
  5.     int i, total = 0;  
  6.   
  7.     for (i = 0; i < num; i++)  
  8.     total += arr[i];  
  9.   
  10.     return total;  
  11. }  
  12.   
  13. int main(void)  
  14. {  
  15.     int a[] = {1, 2, 3, 4};  
  16.     int b[] = {5, 6, 7, 8, 9, 10};  
  17.   
  18.     printf("a[] total value: %d\n", sum(sizeof(a)/sizeof(a[0]), a));  
  19.     printf("b[] total value: %d\n", sum(sizeof(b)/sizeof(b[0]), b));  
  20.   
  21.     return 0;  
  22. }  

    例子输出结果: 

[cpp]  view plain copy
  1. a[] total value: 10  
  2. b[] total value: 45  

    函数sum形参中的arr可以匹配任意的一维整型数组。

    注意,num一定要声明在变长数组arr之前。

-----------------------------------------------------------------------------------------------------------------------------

在一些 C 语言编写的代码中,有时可以看到如下定义的结构:

typedef struct  user_def
{
    
char *
 name;
    
int
 length;
    
char bytes[0
];
} user_def_t
;

这个 bytes 是什么意思?我们知道 0 == sizeof(bytes),那么 bytes 仅仅是为了定义结构的尾地址吗?

不是的。这里的 bytes 是作为扩展数组用的。请看如下代码:

int alloc_user_def_t(user_def_t * p, int  length)
{
    p 
= (user_def_t)malloc(sizeof(user_def_t) +
 length);
    
if (NULL ==
 p)
    {
        
return -1
;
    }

    p
->name =
 NULL;
    p
->length =
 length;
    memset(p
->bytes, 0
, length);
    
return 0
;
}

是不是很酷?同样,也可以把 name 域的值附在结构后面:

int alloc_user_def_t(user_def_t * p, char * name, int length)
{
    p = (user_def_t)malloc(sizeof(user_def_t) + strlen(name) + length + 1);
    if (NULL == p)
    {
        return -1;
    }
    p->name = p + sizeof(user_def_t) + length;
    memcpy(p->name, name, strlen(name) + 1);   /* 别忘了'/0' */
    p->length = length;
    memset(p->bytes, 0, length);
    return 0;
}

总结:在某一结构末尾如定义类似 char bytes[0] 的零长数组,表示该结构不定长,可通过数组的方式进行扩展。结构中必包含一个长度信息。结构本身类似于一个信息头。同时,此结构只能通过堆方式分配内存。

========================

1.零长度数组不是所有的c标准都支持,gnu c支持,ansi c不支持,c++不支持。

 

2.可以把结构体中定义了零长度数组的地方 视为结构体的结尾,在它之后最好不要再定义任何字段。因为零长度数组用来动态的添加数据,一旦添加数据后,零长度数组字段之后定义的字段如果之前赋了值,那么这个值就会被改掉。(结构体的里面的数据在内存中按顺序存储的。)

 

3.如何扩展:

可以直接使用数组方式添加信息:

p->bytes[0] = 'a';

p->bytes[1] = 'b';

也可以通过memcpy,strcpy等函数拷贝方式来添加。

 

4.等价写法:

typedef struct user_def{

    char* name;

    int      length;

    char   bytes[];

}user_def_t;

把char bytes[0] 写成 char bytes[]也可以,但是这样写的话在slickedit里面调试时,查看结构体的成员时是看不到bytes[]这个成员的,写成bytes[0]的话就可以。

  


---------------------------------------------------------------------------

在标准 C 和 C++ 中,不允许用 0 长度数组,但在 GNU C 中,却可以定义 0 长度数组。比如:

引用
struct  line  { 
          int  length ;
          char  contents  [  0  ]; 
}


0 长度数组不占有空间,从打印 sizeof (struct line) 可以看到这个结构体的长度为 4,这 4 字节空间属于整型量 length 。那么结构体里最后的 0 长度字符数组 contents[0] 用来做什么呢?答案是,它可以用来指向一段由你自己分配的 buffer 空间。如:

引用
int  this_length  =  60 ;
struct  line  *  thisline  = ( struct  line  * ) malloc ( sizeof ( struct  line )  +  this_length );
thisline->length = this_length;


这样,就开辟了 64 字节空间。前面 4 个字节为 this_length 用,后面 60 个字节被 contents 数组用。经过空间分配后,就相当于把结构体定义为:

引用
struct  line  { 
          int  length ;
          char  contents  [  60  ]; 
}


顺便看一下这个空间的分布,做如下打印:

引用
printf ( "thisline 指向的地址为 %p  /n  "  ,  thisline );
printf ( "thisline指向的第一个元素值为:%d  /n  "  ,  thisline  ->  length );
printf ( "该元素的地址为%p  /n  "  ,  &  thisline  ->  length );
printf ( "%p  /n  "  ,  &  thisline  ->  contents );
printf ( "%p  /n  "  ,  &  thisline  ->  contents  [  0  ]); 
printf ( "%p  /n  "  ,  &  thisline  ->  contents  [  1  ]);


输出为:

引用
thisline 指向的地址为 0x8780008
thisline指向的第一个元素值为:60
该元素的地址为0x8780008
0x878000c
0x878000c
0x878000d


从输出可以看到,thisline-contents 只是一个“不占有空间“(其特性由编译器决定)的常量指针。在这里,它表示接着整型值后的起始地址。

现在,我们可以使用这个数组了,比如给这个数组的前几个元素赋下值:

引用
char  c  =  'x' ;
int  i ;

thisline  ->  contents  [  0  ]  =  c ;
c  =  'y' ;
thisline  ->  contents  [  1  ]  =  c ;
c  =  'z' ;
thisline  ->  contents  [  2  ]  =  c ;
for ( i  =  0 ;  i  <  3 ;  i  ++ )
       printf ( "%c  /n  "  ,  thisline  ->  contents  [  i  ]);



还有一个问题需要探讨。既然用了 malloc() 函数,那么在最后就会使用 free() 函数来释放掉所申请的内存空间。那么,在这里,是不是只是用 free(thisline) 就能达到目的了呢?下面做一个实验:
首先在程序中,将分配缓冲区的首地址备份起来:

引用
struct  line  *  thisline_bak  =  thisline ;


现在做如下释放然后看一下空间中首元素(length)的值:

引用
free(thisline);
printf ( "%d  /n  "  ,  thisline_bak  ->  length );


一般在打印里可以看到 length 的输出值为 0 ,这也说明,首个元素的内存空间确实被释放了。在这里,严格的说,这个指针已经不能再这么用,因为它指向了一个被释放的内存空间。

但是,如果我们再次运行

引用
for ( i  =  0 ;  i  <  3 ;  i  ++ )
       printf ( "%c  /n  "  ,  thisline  ->  contents  [  i  ]);


这 个打印,发现仍然能够正常的打印出值来,从而说明了,后面申请的数组缓冲区还没释放。原因是,由于分配的空间返回的指针被强制转换为 struct line 类型,且所得到的空间并不是平缓的(包含的不是同一种元素类型的空间)。在这里的空间组成是,一个整型值 + 后面一大块字符型缓冲区。所以,在释放时,free() 函数认为 *thisline 只是一个指向 4 字节整型值的空间。因而,我们要完全释放整个申请的空间,还需要:

引用
free (thisline->contents);


此后,如果再次打印之前那 3 个赋值的元素时,会发现段错误,或者是类似下面的错误:

引用
*** glibc detected *** ./array4: free(): invalid pointer: 0x09e4300c ***
======= Backtrace: =========
/lib/tls/i686/cmov/libc.so.6(+0x6b591)[0x5b8591]
/lib/tls/i686/cmov/libc.so.6(+0x6cde8)[0x5b9de8]
/lib/tls/i686/cmov/libc.so.6(cfree+0x6d)[0x5bcecd]
./array4[0x80485ad]
/lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6)[0x563bd6]
./array4[0x80483c1]
======= Memory map: ========
... ...


所以,在不用这段空间时,可如下释放:

引用

free (thisline->contents);   /*先释放掉后面的数组buffer*/
free(thisline);    /*再释放掉 4 字节整型空间*/



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值