|
我是老温,一名热爱学习的嵌入式工程师( r, a( Q' c$ v; ?+ Y, F' S
关注我,一起变得更加优秀!不知道各位工程师大佬在平时的嵌入式软件开发中,是否有使用过柔性数组(可能比较少?),关于柔性数组的灵活运用,且看下文。
% \3 Q8 o' Q) ?1 W" ]- O零长度数组概念:众所周知, GNU/GCC 在标准的 C/C++ 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展.& s1 \: Y$ X" N& k- Q+ s
多数情况下, 其应用在变长数组中, 其定义如下:
% l) W: u9 s2 Cstruct Packet
K* j! n6 {" W9 f3 W4 X) c{
, \, o$ v- e4 h( v4 }& t4 P7 `+ l/ v int state;
* L; B$ l" u$ ^8 b' s int len;+ G7 _2 A4 |9 Q( J0 c1 U
char cData[0]; //这里的0长结构体就为变长结构体提供了非常好的支持5 N; ?; q1 `* p* S- J9 `3 z0 o
};
7 ^5 J; n' ]9 D) ?$ C+ k首先对 0 长度数组, 也叫柔性数组,做一个解释 :4 W# x( l/ T, |6 {7 B6 G
用途 : 长度为0的数组的主要用途是为了满足需要变长度的结构体;
! ^5 s" M( e; W用法 : 在一个结构体的最后,声明一个长度为 0 的数组, 就可以使得这个结构体是可变长的. 对于编译器来说, 此时长度为 0 的数组并不占用空间, 因为数组名本身不占空间, 它只是一个偏移量, 数组名这个符号本身代表了一个不可修改的地址常量9 x* y$ J4 Q. D; D7 Z1 K# G5 J
(注意 : 数组名永远都不会是指针!), 但对于这个数组的大小, 我们可以进行动态分配。
8 r0 C: S' `7 [: E" t9 A- V注意 :如果结构体是通过 calloc、malloc 或 者 new 等动态分配方式生成,在不需要时要释放相应的空间。6 d$ f3 E7 L l1 c# i1 F* J
优点 :比起在结构体中声明一个指针变量、再进行动态分 配的办法,这种方法效率要高。因为在访问数组内容时,不需要间接访问,避免了两次访存。8 S" {7 P$ b( X
缺点 :在结构体中,数组为 0 的数组必须在最后声明,使用上有一定限制。, w! \2 j9 a- X: q
对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可修改的地址常量!
' U0 E5 g# o g: b3 U( B0 长度数组的用途:我们设想这样一个场景, 我们在网络通信过程中使用的数据缓冲区, 缓冲区包括一个 len 字段和 data 字段, 分别标识数据的长度和传输的数据, 我们常见的有几种设计思路:
# [1 S6 B$ q; S定长数据缓冲区, 设置一个足够大小 MAX_LENGTH 的数据缓冲区) T+ C- ^8 U8 i3 e; q! s, b6 d
设置一个指向实际数据的指针, 每次使用时, 按照数据的长度动态的开辟数据缓冲区的空间/ c v) A8 w( G; ]" A
我们从实际场景中应用的设计来考虑他们的优劣. 主要考虑的有, 缓冲区空间的开辟、释放和访问。. J' Z/ C4 n) {6 \
1、定长包(开辟空间, 释放, 访问):比如我要发送 1024 字节的数据, 如果用定长包, 假设定长包的长度 MAX_LENGTH 为 2048, 就会浪费 1024 个字节的空间, 也会造成不必要的流量浪费:. o2 L& `4 R! G% Y# V4 C- ]
数据结构定义:4 X+ S, k* Q3 z: c5 G: x
// 定长缓冲区# ~& Z, ?! g3 r8 g" R1 g8 T( T
struct max_buffer" \+ H# K: D. K' ]
{" O5 g8 Q) E6 P5 M8 Q3 r
int len; * ?( q' l7 I7 s' }/ {
char data[MAX_LENGTH];2 |- _1 R; ?! k! `" d0 h! N
};数据结构大小:考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH7 V, ~" ? o q) A4 ~6 R
由于考虑到数据的溢出, 变长数据包中的 data 数组长度一般会设置得足够长足以容纳最大的数据, 因此 max_buffer 中的 data 数组很多情况下都没有填满数据, 因此造成了浪费。+ A8 W6 G( E1 d5 r$ x7 T
数据包的构造:假如我们要发送 CURR_LENGTH = 1024 个字节, 我们如何构造这个数据包呢;一般来说, 我们会返回一个指向缓冲区数据结构 max_buffer 的指针:
; I1 h! ^# S( f" X. W( C8 _// 开辟
7 {* t$ l: N- N! ^if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL) : A; K+ l) D, I- h
{
. N# C; D; x/ A( N4 e7 V6 v& ` mbuffer->len = CURR_LENGTH;
0 {7 t7 P+ l1 d5 @/ g4 S memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
5 E9 {* C- X5 ?) {! w% l V F6 W printf("%d, %s
! \, Y# k ?; |' u", mbuffer->len, mbuffer->data);
" B& Z, H% f2 Y3 e% L5 u* |}
8 W. c3 a$ T6 `/ w+ h( i1 ]' l9 h访问:这段内存要分两部分使用;前部分 4 个字节 p->len, 作为包头(就是多出来的那部分),这个包头是用来描述紧接着包头后面的数据部分的长度,这里是 1024, 所以前四个字节赋值为 1024 (既然我们要构造不定长数据包,那么这个包到底有多长呢,因此,我们就必须通过一个变量来表明这个数据包的长度,这就是 len 的作用);而紧接其后的内存是真正的数据部分, 通过 p->data, 最后, 进行一个 memcpy() 内存拷贝, 把要发送的数据填入到这段内存当中
X2 \3 i8 [: q, @# ?" Y释放:那么当使用完毕释放数据的空间的时候, 直接释放就可以了7 p" d0 O6 n$ e2 g8 t) N
// 销毁% y g" I: M7 T( ^3 R0 `
free(mbuffer);
. f1 V" t! H! @; Pmbuffer = NULL;: t& l# O2 f+ _ X5 Q! ?$ _ i
2、小结:使用定长数组, 作为数据缓冲区, 为了避免造成缓冲区溢出, 数组的大小一般设为足够的空间 MAX_LENGTH, 而实际使用过程中, 达到 MAX_LENGTH 长度的数据很少, 那么多数情况下, 缓冲区的大部分空间都是浪费掉的7 m% W& ^ j5 F8 m. ]
但是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操作) `. M/ Z! d7 l0 Z7 Q/ ^% C
3、 指针数据包(开辟空间, 释放, 访问):如果你将上面的长度为 MAX_LENGTH 的定长数组换为指针, 每次使用时动态的开辟CURR_LENGTH 大小的空间, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空间的浪费, 只浪费了一个指针域的空间:
( C2 j0 S& y4 i( T B2 `' n! [数据包定义:
t( k- o" T. p+ [" b5 r5 `struct point_buffer- X: ?4 c8 r& q
{' J d0 U0 U- [9 H) m
int len;/ z5 h7 m& N) K4 i3 c
char *data;
2 E7 O5 p" d; z6 b2 }2 w5 U};- [- k; V3 b/ p
数据结构大小:考虑对齐, 那么数据结构的大小 >= sizeof(int) + sizeof(char *)
# ^' h3 A, c# v2 @空间分配:但是也造成了使用在分配内存时,需采用两步) T8 o# n$ l* D; Q Z% h% q
// =====================& {: Z# W6 Y, N' M# z6 d8 Z8 n
// 指针数组 占用-开辟-销毁
" x5 p0 v/ R; S// =====================: \3 h7 e) f$ W0 l
/// 占用
% C5 W3 R! N& U; `* tprintf("the length of struct test3:%d$ j7 \# ~! o6 Q& S- z
",sizeof(struct point_buffer));8 E M/ i3 k5 z# ~) ~+ }0 z
/// 开辟
: f' B* e& I2 B/ \) zif ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
$ n" C7 b! a: Q( H1 _2 J8 j$ l{" f6 M9 |. E, U2 |6 [
pbuffer->len = CURR_LENGTH;
6 h- K' j9 Z; F; K/ e' Y if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
6 ?* {7 X) Q. |( S- ~9 |; }! j8 C {
' E# r* l8 O) s9 W9 z memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
9 H" N: ^- I) @' } printf("%d, %s/ d; u0 W2 U; D' h" }5 H& _# k- T5 J; f
", pbuffer->len, pbuffer->data);2 m U9 M( \3 ?0 [
}" K+ F. [0 m0 L1 M
}; k1 X' h3 n* F; X( `% f
首先, 需为结构体分配一块内存空间;其次再为结构体中的成员变量分配内存空间。) w7 U+ Z5 f/ N# N
这样两次分配的内存是不连续的, 需要分别对其进行管理. 当使用长度为的数组时, 则是采用一次分配的原则, 一次性将所需的内存全部分配给它。
: \1 Q6 U& O3 M, ?" p释放:相反, 释放时也是一样的:
0 ?' t: p; v' v2 V+ w% \/// 销毁; L4 g# j( ^% X
free(pbuffer->data); d, o- u2 Y2 ]! Q' k) O P3 T
free(pbuffer);% _: A. j' N8 G
pbuffer = NULL;
0 F% T% `* O# L' e" F+ |小结:
$ {' f( M% Y. |- o4 ~$ f* G- 使用指针结果作为缓冲区, 只多使用了一个指针大小的空间, 无需使用 MAX_LENGTH 长度的数组, 不会造成空间的大量浪费。% j6 g2 D: |( N. x! u/ }
但那是开辟空间时, 需要额外开辟数据域的空间, 施放时候也需要显示释放数据域的空间, 但是实际使用过程中, 往往在函数中开辟空间, 然后返回给使用者指向 struct point_buffer 的指针, 这时候我们并不能假定使用者了解我们开辟的细节, 并按照约定的操作释放空间, 因此使用起来多有不便, 甚至造成内存泄漏。
6 @4 S& S- _( {- k; Q3 f# N4、变长数据缓冲区(开辟空间, 释放, 访问)定长数组使用方便, 但是却浪费空间, 指针形式只多使用了一个指针的空间, 不会造成大量空间分浪费, 但是使用起来需要多次分配, 多次释放, 那么有没有一种实现方式能够既不浪费空间, 又使用方便的呢?; x: d# M6 p9 j, P% i: C/ x
GNU C 的 0 长度数组, 也叫变长数组, 柔性数组就是这样一个扩展。对于 0 长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:
1 K9 T: l; E$ z$ e- X数据结构定义:# \+ R) e7 J' y( D6 U
// 0长度数组5 y' P" V$ u& g& M1 j" c
struct zero_buffer
7 M; S/ | n7 F ~5 C% R6 Z. I* D! N6 P{& a& B) W& a* [, g3 Q1 E% u
int len;4 N+ K L: Y. z# y) \
char data[0];' v; O2 V5 f! c7 C
};( x( I1 W8 U. j" o4 U9 A
数据结构大小:这样的变长数组常用于网络通信中构造不定长数据包, 不会浪费空间浪费网络流量, 因为 char data[0]; 只是个数组名, 是不占用存储空间的:
( C! Z+ Q$ f* I v+ O; Z/ qsizeof(struct zero_buffer) = sizeof(int)+ e" M- N1 y1 D! [& J% x$ g& q
开辟空间:那么我们使用的时候, 只需要开辟一次空间即可! E9 K" E# M6 @1 Y
/// 开辟9 m" A3 O% w. s$ o3 K1 {
if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)$ v/ C6 z: l6 w" D9 f
{+ E2 g' }) G, m8 V
zbuffer->len = CURR_LENGTH;
# t, N' O5 Q7 K7 v memcpy(zbuffer->data, "Hello World", CURR_LENGTH);
; F$ B* C# R1 F7 y, L) ? printf("%d, %s
5 V! t' y A4 X( _6 L0 j3 H) `- o", zbuffer->len, zbuffer->data);
; X2 @) e$ k W4 b, z' C( k}' {3 O' Z3 s! A1 u( U
释放空间:释放空间也是一样的, 一次释放即可6 E9 Y w1 a( ?4 r' t F8 _
/// 销毁
1 g' ?0 N4 j2 _* _3 x. Dfree(zbuffer);. o$ G( i+ m' u( A0 L% Q
zbuffer = NULL;
: F `4 t$ G1 F; d6 [: M总结:7 m) K3 U6 X: B* B6 @- J
// zero_length_array.c6 a/ a# d8 q9 v7 u) m
#include $ b; \4 g4 o( X. \( ^) U
#include
2 [* p! u% t8 @: \5 c% z5 x#define MAX_LENGTH 10244 H% y! k; l! ]* P
#define CURR_LENGTH 512
' Q% N/ P Y; c! `6 n% x: j0 L// 0长度数组
/ V4 v- _9 w0 r0 I7 u) _struct zero_buffer( z* X6 N0 k1 C8 o& |
{# X) Q4 R0 k3 n( A* W& ]' _; x% T# ~
int len;
2 H0 U$ }1 {3 r5 L7 T6 \char data[0];
3 ~. G. N0 G* D7 m v}__attribute((packed));9 q! [' Y; J% o- ~) M+ ?! y
// 定长数组* D3 p; |& |3 ]# D3 c0 y
struct max_buffer0 _3 O& i4 g& q9 V
{
: S; D2 a5 f( z( qint len;
' r& y$ @4 W7 q0 v: ]: t+ n5 ]char data[MAX_LENGTH];
5 {; c' b0 q S7 \5 {, n}__attribute((packed));( P! ^2 ?9 ]1 R# d
// 指针数组
& z3 R+ s0 d" O+ ^& ostruct point_buffer* b* R5 o% \$ X: n" N( g( O% j6 K
{
+ m' O( ]: W( }; M" I8 Eint len;- V, ^5 V5 u2 h# n
char *data;
: d- y: d) H6 a. E, m}__attribute((packed));
* o( D# l; D1 Z3 S S% Nint main(void)
2 W0 v/ u r/ i* w2 \" L. i{( ^0 b# P; V& G P5 ~
struct zero_buffer *zbuffer = NULL;$ q* U! g) ], T7 r: [1 ?
struct max_buffer *mbuffer = NULL;8 `4 @- F* [, i# }
struct point_buffer *pbuffer = NULL;) ?5 R3 e' i1 N. E# M5 T9 l+ [
// =====================6 p' q f/ ^- n% j# H3 s4 I
// 0长度数组 占用-开辟-销毁
9 o2 w; g7 C$ g% U5 L // =====================
7 d% X. D7 F/ x7 }! F: u: v) [ /// 占用
& [9 D8 [: \* Q( k4 A6 p' T printf("the length of struct test1:%d6 q, {% E, i/ ~: O
",sizeof(struct zero_buffer));6 \5 k% n( V' C6 H* s
/// 开辟
1 V* r' C8 D& c* ~; D7 P- y! M if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)1 l' W' \$ o( S
{
0 G) H. s t( j( q1 v' k3 s zbuffer->len = CURR_LENGTH;2 l; M X) u4 P% d
memcpy(zbuffer->data, "Hello World", CURR_LENGTH);: _1 g) l, T# c' }' P- |
printf("%d, %s
9 l$ H, e& f, W9 U G* ^* j; X", zbuffer->len, zbuffer->data);
& H$ w9 w! ~, @ }
0 t) \2 o0 y, D' n8 A& ?, B /// 销毁0 I5 y. ?, c9 H! r1 j
free(zbuffer);$ o$ y3 t5 U5 C0 y
zbuffer = NULL;* X) [) Y7 a$ b ^" ~9 f, Y
// =====================
1 b" W- x) ~4 |7 f // 定长数组 占用-开辟-销毁& i& N. o% j- Y+ L$ A
// =====================
7 K9 U8 n6 S5 H: e /// 占用
% Y% a' u5 i' Q' _8 C! H* `, f printf("the length of struct test2:%d
* T: g6 J/ J4 E8 X+ e",sizeof(struct max_buffer));- f0 y6 U1 J3 Y2 i/ M
/// 开辟
, }: R* i* l) W6 u& p- A# i8 z6 e if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
9 W# {% W' u; k: D' m; m {
) e1 t; ^* U' j i3 ^3 R" h: x1 \; u mbuffer->len = CURR_LENGTH;# P' c" `: T3 x P+ |" K+ l: C
memcpy(mbuffer->data, "Hello World", CURR_LENGTH);# ?4 e; n1 L o3 j$ E9 S! T
printf("%d, %s
4 r# q0 u2 [, @", mbuffer->len, mbuffer->data);
/ @$ ]& A: I! o7 Z4 j }4 w5 N4 V n. d+ A/ ^
/// 销毁, o1 R/ U3 e9 W
free(mbuffer);
2 w% C1 F8 f8 h$ Z mbuffer = NULL;/ w: U* u q2 q9 ]7 T
// =====================
7 o" O/ l5 Z, T" Q6 N9 ]* _" Q // 指针数组 占用-开辟-销毁
( O1 S( K# o2 k% ?* K6 p // =====================
0 d% U5 S, h: N8 \ /// 占用8 d X1 v) U0 b o
printf("the length of struct test3:%d
' X ?1 X5 v: ~+ Y4 k |" A3 |",sizeof(struct point_buffer));% A3 R; [) @+ ?' c1 Z
/// 开辟
2 O8 A- M: {$ h" ^) E" Q% x* b( P if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
$ i# S# i4 c1 w+ V; b3 ?4 q {( l9 }; A% m/ C. r2 x, n
pbuffer->len = CURR_LENGTH;# I, F8 P$ F8 ?" y; P! ?
if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL), Y( t3 n# V$ n# L5 k3 e# ?' B) V8 f
{
( L& G1 _* e5 Q Q, Z5 [ memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
/ B3 X! o! H/ \- l' y printf("%d, %s. o9 @0 k8 y7 N: O" _. a l
", pbuffer->len, pbuffer->data);
- v& B. d P w m" X+ g }
/ o* [9 f0 |) L, v/ i" w }
4 ?4 j' `. N+ F2 h /// 销毁+ m# L1 d$ ~& G2 u8 p
free(pbuffer->data); T5 p8 G& a% j q# Q0 N' N
free(pbuffer);" w! e9 d% I( b* l. A
pbuffer = NULL;
( b) M# i5 O# t: }5 T5 M return EXIT_SUCCESS;
, C, v7 H ]* c' B. h% c7 n4 ~0 `}
8 u. A3 C6 D2 a& o# s
4 W- h8 Y7 m5 ~( x- B5 X+ }% Z
qli2npwbrqk640362324.png
' {# p* C3 A) J! f* O0 s" b+ w3 Y
) G" r: b# i7 R7 R
长度为 0 的数组并不占有内存空间, 而指针方式需要占用内存空间.( H' I. d0 u( Q" i
对于长度为 0 数组, 在申请内存空间时, 采用一次性分配的原则进行; 对于包含指针的结构体, 在申请空间时需分别进行, 释放时也需分别释放.
2 `+ ~! J3 m: l& Q" O- `对于长度为 0 的数组的访问可采用数组方式进行" o/ A& I( ^) G3 j1 W
GNU Document中 变长数组的支持:参考:0 ^7 {) H6 z. R% t
6.17 Arrays of Length Zero
9 `5 ]6 [7 W3 L' e2 \6 J& g1 J' AC Struct Hack – Structure with variable length array Q, o2 F' C9 _2 }. }
在 C90 之前, 并不支持 0 长度的数组, 0 长度数组是 GNU C 的一个扩展, 因此早期的编译器中是无法通过编译的;对于 GNU C 增加的扩展, GCC 提供了编译选项来明确的标识出他们:
8 r; z/ j3 e8 R) f-pedantic 选项,那么使用了扩展语法的地方将产生相应的警告信息
# ]+ Y( y) b, i+ C-Wall 使用它能够使 GCC 产生尽可能多的警告信息
& d2 X* I B9 t8 J-Werror, 它要求 GCC 将所有的警告当成错误进行处理
% ?( }% b! d3 [9 F- l// 1.c
n5 v8 M* j0 ^+ Y! k& h#include 1 @& ^+ g l$ v
#include
5 j# z# k; n( G- A2 x2 dint main(void)
' {% _$ n& ~$ e6 l7 P9 x{+ q* N. W( h: x& G7 k3 ^+ ]+ O9 B, R
char a[0];
, Z$ u0 Q8 s9 n& {6 d% G printf("%ld", sizeof(a));. f f9 W7 g6 G1 N6 d- e. t+ ]5 H
return EXIT_SUCCESS;
0 ?9 I3 r; y3 k# x4 q}; p0 U/ f# r$ j6 C5 u! S* G
我们来编译:
' R+ u/ U2 |& I" x1 e9 }/ U% I# 显示所有警告
- n, u, D m. E9 r# T9 wgcc 1.c -Wall
8 B1 t/ I% m5 y5 Q" Z* m#none warning and error; w5 O; b$ p# d& E5 W# f# Y6 ^8 M
# 对GNU C的扩展显示警告& d" ?, }) L6 H) B# P+ ^; }
gcc 1.c -Wall -pedantic$ f+ h) H; F1 |$ p2 K
1.c: In function ‘main’: Z |5 G3 X9 m$ u |, ~6 o: P# }
1.c:7: warning: ISO C forbids zero-size array ‘a’
: _" Y% ^# L @* D5 E- g# 显示所有警告同时GNU C的扩展显示警告, 将警告用 error 显示
) y! `: d: R! o1 Ngcc 1.c -Werror -Wall -pedantic
3 P( e) Y1 ?% s! H; V. xcc1: warnings being treated as errors
* m& M% B' o' X; w1.c: In function ‘main’:
) I5 p" C6 Q- n) m0 e. Q1.c:7: error: ISO C forbids zero-size array ‘a’
8 u; d) w- i! n$ p( f" q f/ D+ D) N1 _- v+ D% R- T
x5dod34ffci640362424.png
4 a6 b [1 n Y" X0长度数组其实就是灵活地运用数组指向的是其后面连续的内存空间:
( D3 |% n7 [% c1 E& @. Hstruct buffer7 Q/ l$ y8 i- g# `: G" x: Z
{
/ \: o: o# j/ z- G5 ?: L1 u int len;, V0 ]( y; P2 ^: \
char data[0];
7 V- ^! `6 I8 D$ _};
, t& q: f, }$ p" W* W+ z4 X在早期没引入 0 长度数组的时候, 大家是通过定长数组和指针的方式来解决的, 但是:3 i S) O+ M# }4 ^& B' T
定长数组定义了一个足够大的缓冲区, 这样使用方便, 但是每次都造成空间的浪费* _! A* b( i1 }- H6 c" {8 O
指针的方式, 要求程序员在释放空间时必须进行多次的 free 操作, 而我们在使用的过程中往往在函数中返回了指向缓冲区的指针, 我们并不能保证每个人都理解并遵从我们的释放方式。8 B5 T& E" G* j$ O6 A" \7 E0 t
所以 GNU 就对其进行了 0 长度数组的扩展. 当使用 data[0] 的时候, 也就是 0 长度数组的时候,0长度数组作为数组名, 并不占用存储空间。( a; P: c {% d" ?- T2 p! ]( ?' L
在 C99 之后,也加了类似的扩展,只不过用的是 char payload[] 这种形式(所以如果你在编译的时候确实需要用到 -pedantic参数,那么你可以将 char payload[0] 类型改成 char payload[] , 这样就可以编译通过了,当然你的编译器必须支持 C99 标准的,如果太古老的编译器,那可能不支持了); u; F; \1 ?2 x: @' {- g% H% T8 K" k
// 2.c payload
# T$ Q ~% ]/ ?. d#include #
2 o0 P; n! a" z3 {1 z' finclude
9 c2 T7 |5 ~: ]; V* _struct payload2 L* v+ I, j0 o, q6 ?% ~1 S9 v% y
{, i, h( E4 h9 U- c1 |( o
int len;
3 l. R) O' t( G6 { char data[];6 z4 e9 v1 I+ x) b! a( C$ \
};
+ ]2 }+ ]- o6 fint main(void)
: U, {' `. C2 K$ ^9 {1 F( J8 T{
" z8 w- X0 G- Y. u struct payload pay;
' h% K8 x) w# h' f: L9 Y printf("%ld", sizeof(pay));9 k- a; o9 N- r4 p g
return EXIT_SUCCESS;
, Z# b- Z9 f. \; Z# w0 b3 D+ S9 G3 P4 o}* I0 w- l$ K" J; i
使用 -pedantic 编译后, 不出现警告, 说明这种语法是 C 标准的& I: J2 x/ ^7 M$ z% N: i" J/ X" J
gcc 2.c -pedantic -std=c99# `0 q V8 W- x H4 ?
7 y9 O" @9 `. T! R/ r1 |: s6 Y6 a
ajo2vddystc640362524.png
% F f6 ]6 m* c' Z8 X+ ?7 N" j所以结构体的末尾, 就是指向了其后面的内存数据。因此我们可以很好的将该类型的结构体作为数据报文的头格式,并且最后一个成员变量,也就刚好是数据内容了.2 }+ r& k. T' o9 ~' V; s
GNU 手册还提供了另外两个结构体来说明,更容易看懂意思:# S1 R8 {' Y3 y, B
struct f1
' g- {8 s4 V7 {; x) b/ w; D{
9 f- m0 D; h" S7 o) h: x! x int x;
3 ^, c2 }+ }- L. d% h int y[];3 M: e% T3 f; l1 p, J
} f1 = { 1, { 2, 3, 4 } };6 H( G+ C- g$ W0 \4 ]4 r# _- H
struct f2
0 ~* a: Z! Q: l. m, @) [3 X{
7 J, k" t- {7 N: q6 w struct f1 f1;/ m1 k/ C# ^$ t9 ?/ `5 R
int data[3];+ U8 |8 m+ `& s. F6 w; ?/ O
} f2 = { { 1 }, { 5, 6, 7 } };
1 V; I! g- G8 [0 L$ H我把 f2 里面的 2,3,4 改成了 5,6,7 以示区分。如果你把数据打出来。即如下的信息:1 r/ y# P( {2 I# z. l5 S) ^) T
f1.x = 19 x0 Z0 M2 K4 q3 P
f1.y[0] = 2
' n/ H: Y- \) T! \! m4 K( qf1.y[1] = 3
4 T( D& q4 T, F& N5 B5 o" Zf1.y[2] = 4
) t' {+ I$ n9 p也就是 f1.y 指向的是 {2,3,4} 这块内存中的数据。所以我们就可以轻易的得到,f2.f1.y 指向的数据也就是正好 f2.data 的内容了。打印出来的数据:
; `7 L9 Q% i! l6 c8 tf2.f1.x = 1
' d' E0 E! B d9 N7 N2 U, ^+ jf2.f1.y[0] = 5
4 [2 a; }( b3 c4 hf2.f1.y[1] = 6
& v* D$ g9 U* V$ @6 Qf2.f1.y[2] = 7
' b4 P4 y* u$ L1 y) L如果你不是很确认其是否占用空间. 你可以用 sizeof 来计算一下。就可以知道 sizeof(struct f1)=4,也就是 int y[]其实是不占用空间的。但是这个 0 长度的数组,必须放在结构体的末尾。如果你没有把它放在末尾的话。编译的时候,会有如下的错误:# a* x1 s" j" E* T$ L" @% P8 @
main.c:37:9: error: flexible array member not at end of struct6 K& @- J/ j. ]& s+ @
int y[];+ R" j8 h, ]7 _, ~! L, ]7 A$ w* h2 I
^
2 P' b4 D0 G; q. L6 s) \到这边,你可能会有疑问,如果将 struct f1 中的 int y[] 替换成 int *y ,又会是如何?这就涉及到数组和指针的问题了. 有时候吧,这两个是一样的,有时候又有区别。
( D& e5 L ~. E6 B" {首先要说明的是,支持 0 长度数组的扩展,重点在数组,也就是不能用 int *y 指针来替换。sizeof 的长度就不一样了。把struct f1 改成这样:
6 y2 o. O4 C# n8 }6 i+ M wstruct f3% K# F6 D6 P; j J, Z
{
$ m; h, s t, s int x;3 }0 ~% A3 \, q2 q
int *y;! @7 }+ o: }' U; I" l( f8 _# O
};. S6 a3 q! Z3 U: e7 E( m
在 32/64 位下, int 均是 4 个字节, sizeof(struct f1)=4,而 sizeof(struct f3)=16; B( t6 I0 [0 A
因为int *y 是指针, 指针在 64 位下, 是 64 位的, sizeof(struct f3) = 16;如果在32位环境的话, sizeof(struct f3) 则是 8 了, sizeof(struct f1) 不变. 所以 int *y 是不能替代 int y[] 的;; J, C0 @ A& V. S# A" r
代码如下:) L: a2 N& q" A1 A
// 3.c
1 V3 Y, m7 c# |1 J9 B. d#include - _% o2 @% O% H
#include
) p* Y) e b4 s! f: x: S, Z |struct f1" O# U9 o# i: R; ?3 X* E
{8 R, g. Y3 H: K8 I
int x;/ C0 ^# V, z: G
int y[];
?# k1 ?. D8 S) X( K; S& L3 k, ~} f1 = { 1, { 2, 3, 4 } };6 v7 Y/ P) P1 }- [7 _8 `0 S1 m
struct f2 ' X3 d! H! d' U* C9 ^7 r) J. H
{
) k5 ~1 D, K' B& u# |; p struct f1 f1;( y9 D0 F) W) Z& {
int data[3];
% u( `- @0 }- y+ [% A} f2 = { { 1 }, { 5, 6, 7 } };
$ ~( w, G: e, I5 e" T5 Sstruct f38 @) {# O5 N: a. H
{
$ L$ j( b, t& Q" b int x;( i+ M# w. ^! E, A0 E& ]9 @
int *y;
( n& U1 g. z# z1 N, L# K};
2 J! U2 n; K" v% V; W ]8 ]$ oint main(void)! i8 r- d: G" y9 P4 v; q# q
{
, H8 ]' R( h7 i+ ?+ V printf("sizeof(f1) = %d
, w% W8 ? o" W5 `", sizeof(struct f1));+ `# w3 f: c( p# @: i3 g$ ^9 m. b
printf("sizeof(f2) = %d
6 J8 z5 H' m$ A1 X", sizeof(struct f2));+ h3 ?2 F& z1 J" s7 |
printf("szieof(f3) = %d
2 R* ^6 ~+ v% E C% l", sizeof(struct f3));
; v. O8 H; A- S0 ?4 F printf("f1.x = %d2 m! o) K4 t( {9 b
", f1.x);
9 s9 w% R5 k! q! C printf("f1.y[0] = %d$ P1 W% b( }, r# e4 f2 h# ]4 {+ W
", f1.y[0]);, m( B7 a7 G: t+ c
printf("f1.y[1] = %d" N- ` F! C4 W4 z! ~% G
", f1.y[1]);
! S9 y7 o! H( H; ? printf("f1.y[2] = %d
% ^! o% l! I+ j. Z( T", f1.y[2]);
, @& @4 o+ x7 a1 W8 ^/ Y printf("f2.f1.x = %d2 e0 o5 O- n; l. [% o& o; Z
", f1.x);
7 I9 x/ a& { `- {& r printf("f2.f1.y[0] = %d2 x3 [0 K3 k3 R0 @( M2 D
", f2.f1.y[0]);& _9 y$ g2 D. H6 v% {+ c) W
printf("f2.f1.y[1] = %d: J( h/ o3 a) O, G/ o' W- F8 o7 O+ q, j
", f2.f1.y[1]);0 j& R+ F0 e- ]# N+ j8 \: P
printf("f2.f1.y[2] = %d
0 r5 F) M# x; G( H* N! {) {", f2.f1.y[2]);
4 |& U( N5 D# T return EXIT_SUCCESS;
0 }+ c/ y- F5 F6 n' w" O+ B3 W}
: w3 U( q. _% G+ _3 U3 K2 \
* a2 c8 r/ E( @2 r* H% b4 E8 E# [5 S
rzu4trhxaoj640362624.png
2 x) a$ G1 w0 P$ E5 z0 长度数组的其他特征:1、为什么 0 长度数组不占用存储空间:0 长度数组与指针实现有什么区别呢, 为什么0长度数组不占用存储空间呢?
1 H( _! j h) g& S" B" y! U其实本质上涉及到的是一个 C 语言里面的数组和指针的区别问题。char a[1] 里面的 a 和 char *b 的 b 相同吗?) ~$ O/ z3 f9 v; I5 W* H* Y/ T
《 Programming Abstractions in C》(Roberts, E. S.,机械工业出版社,2004.6)82页里面说:
8 O" y$ K1 O" Q( x' O“arr is defined to be identical to &arr[0]”.
7 t6 V- ~! d1 d O$ d* ]0 R$ l. N也就是说,char a[1] 里面的 a 实际是一个常量,等于 &a[0] 。而 char *b 是有一个实实在在的指针变量 b 存在。所以,a=b 是不允许的,而 b=a 是允许的。两种变量都支持下标式的访问,那么对于 a[0] 和 b[0]本质上是否有区别?我们可以通过一个例子来说明。. G4 H- Y" U1 h& g5 a
参见如下两个程序 gdb_zero_length_array.c和 gdb_zero_length_array.c:. p6 _3 z7 T1 M/ x- m$ [
// gdb_zero_length_array.c$ p2 O9 p. G. h6 K$ A+ w# i
#include 1 G3 j' W5 m, H n' u- [7 ~
#include . Z! d. }" y/ f( @- `0 U1 b
struct str6 t: E5 H# O7 D
{
8 r& o% a a; Q+ W" R; O! M5 }! k int len;; b, V, s3 X0 d/ ]' Q
char s[0];
6 v; `/ @8 g, O. G: g1 J/ J: O3 ^};
8 f$ A( h4 ?1 z5 N! H6 Zstruct foo
8 T6 k! J6 _) m# H" @{7 \' F- ?" G- q# j: M4 m
struct str *a;9 y0 t J7 W6 F* M7 A9 A
};/ }; v# m7 ?! w3 J# H3 |2 ?& H: E: v
int main(void)
- d: F# _- F6 {4 |{
! e5 G' H7 Z' p* R3 J3 f Y2 j2 N struct foo f = { NULL };
; j. l8 C6 {5 P" t. q9 X printf("sizeof(struct str) = %d
/ u8 |, m4 ]& G; ^4 o", sizeof(struct str));
- E/ B8 }: Q* {1 q printf("before f.a->s.
~/ s+ m) ~0 ~0 |");
* B6 |/ a& P( t if(f.a->s)' k3 E; w" r/ C# y l* k. K `
{
; Z$ t# [2 Q: L3 R# U1 s printf("before printf f.a->s.9 Z1 H) n% C, c$ I* n2 a
");
7 G/ a0 D& u- s, X1 z printf(f.a->s);
6 d1 y; B3 L( J- R: j. h+ ] printf("before printf f.a->s.
K1 y9 [3 j( J* P. S# i");
; H4 U$ L8 R3 \0 ]) { }
# q( L4 r5 O& [; F4 ~' U return EXIT_SUCCESS;) X9 T, r S! H: ~( h7 l0 w1 [
}
, ^$ ?6 s+ V5 |3 O
: D( Q( q( b; A; k
ruq3y42mxo1640362724.png
# K/ r3 a5 ~# ?! A/ W6 k% n' J; C! `2 U& p
// gdb_pzero_length_array.c
1 o* e0 `/ m1 X5 f9 \9 F#include #
$ M. w# S4 a; p0 }- y- ainclude
7 B- W. e1 K' x% A& N- wstruct str& M0 ]5 {$ x' U9 M7 @& r
{
/ u7 {2 @* `" d4 T int len;
+ S b) Q `! X; V* j# f& D char *s;9 y9 {% R i) I
};4 f/ ?- [$ B- n
struct foo
) F, f( B2 m$ e% L, O6 \& O. L% Y{3 ~2 b( W; i9 h
struct str *a;! @* k: k4 l q; c
};
5 a: v6 o7 O5 D. I2 rint main(void)6 r5 X R7 p8 s( u! x3 ^" l6 ^9 c, u
{
L8 ?4 Z1 y: p( x! N' w3 P struct foo f = { NULL };- R; O! ^3 L* V+ V
printf("sizeof(struct str) = %d1 n- v; F3 R! m, d1 m3 X9 |
", sizeof(struct str));
. R8 u( d4 F" \$ h printf("before f.a->s.
( u+ ]" n% b2 A: }% p( q");$ B. m- K3 c" |! }
if (f.a->s)6 v1 U# d5 a' l0 ? g1 t' ^; ^' T2 ^
{, z" k0 [8 ^' L" F" l2 z
printf("before printf f.a->s.: v" V: Z8 h% F$ n* c
");
" R9 j9 V& e# f3 ?7 i* \ printf(f.a->s);
3 c' `$ B* ]0 ? printf("before printf f.a->s.7 G- u. f7 @. C. T m
");( W1 |+ V* A+ [. S
}
+ @8 U7 v: Y4 |( [: L2 p" W return EXIT_SUCCESS;9 H3 a, z! G6 f# J; U( p
}, L" ~% o! j2 H" y1 f
* [4 Q7 j e5 h2 [5 `
wp23a2m1dy5640362824.png
/ d: P& O# X/ s7 Z5 g0 a z2 ]8 Y- U. n* o2 I
可以看到这两个程序虽然都存在访问异常, 但是段错误的位置却不同
2 f: d/ `; W7 u8 X我们将两个程序编译成汇编, 然后 diff 查看他们的汇编代码有何不同
# f* c/ u' R5 ^; K3 V1 B! }gcc -S gdb_zero_length_array.c -o gdb_test.s2 p2 R! `- v" A$ P' b6 g1 w1 q
gcc -S gdb_pzero_length_array.c -o gdb_ptest _! D( z. e: A9 v" w+ G# i
diff gdb_test.s gdb_ptest.s
: v, `" S9 r; {1 E t1c18 k# ]" m2 ~3 W( t! D9 @9 F
"gdb_zero_length_array.c"8 O% Y+ u( ]9 g& k
---
$ e5 K( q$ w: w" k9 _' |> .file "gdb_pzero_length_array.c"! ~6 E/ S4 I8 ^' f3 ~2 _
23c23
4 K( g' M! D4 {* T1 t: ]9 n movl $16, %esi
* R) ^0 \, _2 G4 t2 A30c30
; f, `3 w- g; g( _1 n movq 8(%rax), %rax" K3 w9 b c1 i1 |6 v
36c36
! C' V; | o% e/ q5 O" r6 i" P movq 8(%rax), %rax # printf("sizeof(struct str) = %d- v4 X- [" R" c- b
", sizeof(struct str));% e5 X) n( r$ h7 u7 j
23c23
\/ m1 r" Z0 s$ j! c2 Y7 u#printf("sizeof(struct str) = %d3 f0 L# \2 x+ o! U5 K
", sizeof(struct str));
3 T- c# U C! w) }6 f% a0 |--- ?6 }9 e/ _- t3 m& c1 A5 R0 `
> movl $16, %esi #printf("sizeof(struct str) = %d6 {& e M+ d# T/ s v0 t& U! P- T$ Y2 R
", sizeof(struct str));
3 }# I4 q; H/ @2 U; _$ ^$ H从 64 位系统中, 汇编我们看出, 变长数组结构的大小为 4, 而指针形式的结构大小为 16:" x" U1 Z; B( r4 w" T! \
f.a->s
! ]7 ~4 p7 Q ~( A: a1 \% A; e30c30/36c36
( y% ], x5 j/ \) i" K. F! D movq 8(%rax), %rax
* I; F) f3 K- |# s+ }! E可以看到有:4 o) E2 C( P1 n @% b
对于 char s[0] 来说, 汇编代码用了 addq 指令, addq $4, %rax
: n L# i& v( J2 k对于 char *s 来说,汇编代码用了 movq 指令, movq 8(%rax), %rax& I, r' M, F( h3 _: N
addq 对 %rax + sizeof(struct str), 即 str 结构的末尾即是 char s[0] 的地址, 这一步只是拿到了其地址, 而 movq 则是把地址里的内容放进去, 因此有时也被翻译为 leap 指令, 参见下一例子
3 ?# [+ C* |! l+ z从这里可以看到, 访问成员数组名其实得到的是数组的相对地址, 而访问成员指针其实是相对地址里的内容(这和访问其它非指针或数组的变量是一样的):. p* H5 i( r8 m% X* {
访问相对地址,程序不会 crash,但是,访问一个非法的地址中的内容,程序就会 crash。
$ M, x0 \) t/ j// 4-1.c% |' `/ A; n9 Y, m- F( P
#include
# q: z/ E& D# P2 x, O: D#include 6 [ A) o+ D u [1 w
int main(void)
0 c! N8 D! Q. z% e# z! S6 o{
) Q# b' d: j) y3 c char *a;
# t5 g1 S) I4 F# d' g. }6 [ printf("%p
# U( o S/ ^: k+ e* w* {% J", a);
5 N' K, ?% N& }8 \7 c/ G return EXIT_SUCCESS;
$ u: X- E) n+ y4 p, J9 ]}/ l1 W7 s' m! |
+ C. S3 v0 x5 F$ _# t; f1 E0 `
4-2.c
6 l8 \: ]3 @+ w$ O#include 2 t( v% z; A( {6 @
#include ) J2 [, B p' d) T9 A4 ?8 s
int main(void)1 `( C9 j. t( @! `/ p: x9 D( A4 g
{
5 K2 @' p `* v1 g G0 B char a[0];
$ O+ o8 X& K4 {+ U: N+ H6 U3 i& W printf("%p1 `2 x1 G" b9 q+ C1 _0 d
", a); return EXIT_SUCCESS; ^! d5 e- ?( ~- w/ W0 a. v
}' K+ v. E h% B4 N. o
- a6 f* \' W6 A0 Y' z& t& x
$ diff 4-1.s 4-2.s
! O# U# ?% F/ W5 E( s2 F. P1c1/ W1 j% h1 e4 o+ s
"4-1.c"
/ C$ R# x* Z7 s9 Q+ z* `0 J---
& K4 S5 N$ z% Y# ?# _* h0 D> .file "4-2.c"
' Y, m4 a( Z; |3 Q) X13c13
, H+ F7 T0 u" |/ U$ \7 z9 l7 H; [16, %esp
, O/ A6 x; y* J! v5 q1 t---
) y7 a" G+ z( z, Z' _0 r> subl $32, %esp4 r' D9 |' ~$ [0 O1 ?1 x
15c15
: Q: G) `' `8 @; G1 T; p16(%esp), %eax
, X* _9 G; b9 q; w! p---! v2 V9 x" D* z% Q( M- V4 j
> movl 28(%esp), %eax
% h7 ^: v# j. j- s0 u6 {对于 char a[0] 来说, 汇编代码用了 leal 指令, leal 16(%esp), %eax:
5 Z, ~% S3 \: y' g/ |% x: z7 `对于 char *a 来说,汇编代码用了 movl 指令, movl 28(%esp), %eax
) ~+ d& e& j7 u5 G0 M+ w2、地址优化:// 5-1.c5 l% F) B I3 n9 b: D6 Q7 Z
#include 5 y) z( v( z3 \ E& y
#include % Y0 S+ K1 G% Z4 v8 |( e# ?4 R
int main(void)
0 b- @: x$ n+ s- d6 J1 d{% h$ X% h5 x5 y9 t5 z
char a[0];1 z ~* D' S( Q3 k6 R
printf("%p
/ l3 o) z m" U", a);& E# W( z2 P) s" S
char b[0];! V1 h7 Y" r7 Y( d
printf("%p
4 M& i# B( q& ]! o6 K", b);
9 M$ O1 G$ l/ P3 k# v& T+ i return EXIT_SUCCESS;- U8 g8 [2 X y% m! z
}. E9 ~7 S2 r% R# N" P( n
7 j' M2 ~9 M' D0 }
1tebahiqjoa640362924.png
& M$ H, r4 W( L; q4 ^由于 0 长度数组是 GNU C 的扩展, 不被标准库任可, 那么一些巧妙编写的诡异代码, 其执行结果就是依赖于编译器和优化策略的实现的.
; V, z5 e7 ?$ R4 q/ a比如上面的代码, a 和 b 的地址就会被编译器优化到一处, 因为 a[0] 和 b[0] 对于程序来说是无法使用的, 这让我们想到了什么?
% M1 d% B' R# @4 t$ n: G编译器对于相同字符串常量, 往往地址也是优化到一处, 减少空间占用:- B v- A" o" v- l+ d9 ?
// 5-2.c( }$ V0 A: i7 Y! h. j7 F
#include R6 p" ~0 H* L
#include : j1 ^' O$ J/ U# Z4 z1 K
int main(void)
1 ?- E( ~1 m* b+ @{
( V# V8 Q9 T0 t6 M! T3 L const char *a = "Hello";
& U7 N8 y/ g# Y, W8 f- F3 K printf("%p
, a) {2 @3 u, \3 }", a);
7 U& m: h# F% U6 I2 ?5 y const char *b = "Hello";
) X4 C& l6 e2 K2 `+ l- P6 s9 \6 @ printf("%p( v( ^4 e9 {6 C9 I
", b);
8 p7 U0 V+ r2 [ const char c[] = "Hello";) _9 j' V- `1 x
printf("%p
) J" {; ^) t( W2 J* j$ L", c);6 t% w8 @6 @+ d4 t! ~
return EXIT_SUCCESS;) Q. E- D9 ^! v2 ?
}+ d+ j9 o& O, ^1 y7 N3 X, C N+ H0 q
- D* i! x# [9 V e4 Y+ L3 Y
4svgattu54u640363024.png
* o! e. a# j: u: o0 Y
: n |% _" j; }7 C
--- 文章来源于牛逼的工程师网友
* J4 h a' C' q, R% `% h- `1 ^1 y& s-END-% P6 i! o( {5 d& U, F4 W
往期推荐:点击图片即可跳转阅读 1 ^$ E; k. y0 t( B! D) r+ F
0 Z$ d2 b) [* `: m
7 s( r; [3 i" ~2 w1 P+ X& m+ C$ s
hfvaxxlenvq640363124.jpg
( S, a0 T: e! l' I( v 嵌入式软件,能用“低代码”的方式进行开发吗?
/ V, N+ O- T% d' a / `, ?' F' e2 y/ |" E! y6 O
, A" F* }/ F% v; X R% z
1mr0ta5vqoy640363225.jpg
* `, p: L' k+ L3 `2 g, z0 m0 S2 s. D 很大的一个坑,稍有不注意,又是一次硬件研发失误!! m: c( I5 _ @2 M
5 u- l& y3 r; X5 O( Z - p8 K7 K6 |9 [, C0 ^2 u1 }
. V P- R6 k- B7 G. C
hd0j0sb3ptu640363325.jpg
u' I/ f( J8 ^& y* I8 h
嵌入式 C 语言函数的返回值,也有其应用“潜规则”?
) V' O* `% w, Z2 F3 r* |- J
) U3 Y, I+ R! a' s' v9 C- C我是老温,一名热爱学习的嵌入式工程师
) V8 \3 N8 S4 I, t关注我,一起变得更加优秀!
9 E$ f m! N o' A0 M2 J( `
dzlsazv2icz640363425.jpg
|
|