C语言中长度为零的数组详解 (2)

41 篇文章 0 订阅
16 篇文章 1 订阅

GNU Document中 变长数组的支持:

参考:

6.17 Arrays of Length Zero
C Struct Hack – Structure with variable length array

在 C90 之前, 并不支持 0 长度的数组, 0 长度数组是 GNU C 的一个扩展, 因此早期的编译器中是无法通过编译的;对于 GNU C 增加的扩展, GCC 提供了编译选项来明确的标识出他们:

  • -pedantic 选项,那么使用了扩展语法的地方将产生相应的警告信息

  • -Wall 使用它能够使 GCC 产生尽可能多的警告信息

  • -Werror, 它要求 GCC 将所有的警告当成错误进行处理

 我们来编译:

 

 0长度数组其实就是灵活地运用数组指向的是其后面连续的内存空间:

在早期没引入 0 长度数组的时候, 大家是通过定长数组和指针的方式来解决的, 但是:

  • 定长数组定义了一个足够大的缓冲区, 这样使用方便, 但是每次都造成空间的浪费

  • 指针的方式, 要求程序员在释放空间时必须进行多次的 free 操作, 而我们在使用的过程中往往在函数中返回了指向缓冲区的指针, 我们并不能保证每个人都理解并遵从我们的释放方式。

    所以 GNU 就对其进行了 0 长度数组的扩展. 当使用 data[0] 的时候, 也就是 0 长度数组的时候,0长度数组作为数组名, 并不占用存储空间。

在 C99 之后,也加了类似的扩展,只不过用的是 char payload[] 这种形式(所以如果你在编译的时候确实需要用到 -pedantic参数,那么你可以将 char payload[0] 类型改成 char payload[] , 这样就可以编译通过了,当然你的编译器必须支持 C99 标准的,如果太古老的编译器,那可能不支持了)

 使用 -pedantic 编译后, 不出现警告, 说明这种语法是 C 标准的

所以结构体的末尾, 就是指向了其后面的内存数据。因此我们可以很好的将该类型的结构体作为数据报文的头格式,并且最后一个成员变量,也就刚好是数据内容了.

GNU 手册还提供了另外两个结构体来说明,更容易看懂意思:

 我把 f2 里面的 2,3,4 改成了 5,6,7 以示区分。如果你把数据打出来。即如下的信息:

 也就是 f1.y 指向的是 {2,3,4} 这块内存中的数据。所以我们就可以轻易的得到,f2.f1.y  指向的数据也就是正好 f2.data 的内容了。打印出来的数据:

 如果你不是很确认其是否占用空间. 你可以用 sizeof 来计算一下。就可以知道 sizeof(struct f1)=4,也就是 int y[]其实是不占用空间的。但是这个 0 长度的数组,必须放在结构体的末尾。如果你没有把它放在末尾的话。编译的时候,会有如下的错误:

到这边,你可能会有疑问,如果将 struct f1 中的 int y[] 替换成 int *y ,又会是如何?这就涉及到数组和指针的问题了. 有时候吧,这两个是一样的,有时候又有区别。

首先要说明的是,支持 0 长度数组的扩展,重点在数组,也就是不能用 int *y 指针来替换。sizeof 的长度就不一样了。把struct f1 改成这样:

在 32/64 位下, int 均是 4 个字节,  sizeof(struct f1)=4,而 sizeof(struct f3)=16

因为int *y 是指针, 指针在 64 位下, 是 64 位的, sizeof(struct f3) = 16;如果在32位环境的话, sizeof(struct f3) 则是 8 了, sizeof(struct f1) 不变. 所以 int *y 是不能替代 int y[] 的;

代码如下:

 

0 长度数组的其他特征: 

1、为什么 0 长度数组不占用存储空间:

0 长度数组与指针实现有什么区别呢, 为什么0长度数组不占用存储空间呢?

其实本质上涉及到的是一个 C 语言里面的数组和指针的区别问题。char a[1] 里面的 a 和 char *b 的 b  相同吗?

《 Programming Abstractions in C》(Roberts, E. S.,机械工业出版社,2004.6)82页里面说:

也就是说,char a[1] 里面的 a 实际是一个常量,等于 &a[0] 。而 char *b 是有一个实实在在的指针变量 b 存在。所以,a=b 是不允许的,而 b=a 是允许的。两种变量都支持下标式的访问,那么对于 a[0] 和 b[0]本质上是否有区别?我们可以通过一个例子来说明。

参见如下两个程序 gdb_zero_length_array.c和 gdb_zero_length_array.c

 

 

 

可以看到这两个程序虽然都存在访问异常, 但是段错误的位置却不同

我们将两个程序编译成汇编, 然后 diff 查看他们的汇编代码有何不同

 从 64 位系统中, 汇编我们看出, 变长数组结构的大小为 4, 而指针形式的结构大小为 16:

可以看到有:

  • 对于 char s[0] 来说, 汇编代码用了 addq 指令, addq $4, %rax

  • 对于 char *s 来说,汇编代码用了 movq 指令, movq 8(%rax), %rax

addq 对 %rax + sizeof(struct str), 即 str 结构的末尾即是 char s[0] 的地址, 这一步只是拿到了其地址, 而 movq 则是把地址里的内容放进去, 因此有时也被翻译为 leap 指令, 参见下一例子

从这里可以看到, 访问成员数组名其实得到的是数组的相对地址, 而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的):

  • 访问相对地址,程序不会 crash,但是,访问一个非法的地址中的内容,程序就会 crash

  • 对于 char a[0] 来说, 汇编代码用了 leal 指令, leal 16(%esp), %eax

  • 对于 char *a 来说,汇编代码用了 movl 指令, movl 28(%esp), %eax

2、地址优化:

 

由于 0 长度数组是 GNU C 的扩展, 不被标准库任可, 那么一些巧妙编写的诡异代码, 其执行结果就是依赖于编译器和优化策略的实现的.

比如上面的代码, a 和 b 的地址就会被编译器优化到一处, 因为 a[0] 和 b[0] 对于程序来说是无法使用的, 这让我们想到了什么?

编译器对于相同字符串常量, 往往地址也是优化到一处, 减少空间占用:

 

【学习交流群:769843038】 

【自己收集的网盘免费资料包,需要的自行领取】:

嵌入式物联网 22个STM32项目、大赛作品,【华清远见发放资料包】http://makerschool.mikecrm.com/f4wjYBB 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值