一文帶你了解C語(yǔ)言中的0長(zhǎng)度數(shù)組(可變數(shù)組/柔性數(shù)組)
零長(zhǎng)度數(shù)組概念
眾所周知, GNU/GCC 在標(biāo)準(zhǔn)的 C/C++ 基礎(chǔ)上做了有實(shí)用性的擴(kuò)展, 零長(zhǎng)度數(shù)組(Arrays of Length Zero) 就是其中一個(gè)知名的擴(kuò)展.
多數(shù)情況下, 其應(yīng)用在變長(zhǎng)數(shù)組中, 其定義如下:
struct Packet
{
int state;
int len;
char cData[0]; //這里的0長(zhǎng)結(jié)構(gòu)體就為變長(zhǎng)結(jié)構(gòu)體提供了非常好的支持
};
首先對(duì) 0長(zhǎng)度數(shù)組, 也叫柔性數(shù)組 做一個(gè)解釋 :
- 用途 : 長(zhǎng)度為0的數(shù)組的主要用途是為了滿足需要變長(zhǎng)度的結(jié)構(gòu)體
- 用法 : 在一個(gè)結(jié)構(gòu)體的最后, 申明一個(gè)長(zhǎng)度為0的數(shù)組, 就可以使得這個(gè)結(jié)構(gòu)體是可變長(zhǎng)的. 對(duì)于編譯器來(lái)說(shuō), 此時(shí)長(zhǎng)度為0的數(shù)組并不占用空間, 因?yàn)閿?shù)組名本身不占空間, 它只是一個(gè)偏移量, 數(shù)組名這個(gè)符號(hào)本身代表了一個(gè)不可修改的地址常量
(注意 : 數(shù)組名永遠(yuǎn)都不會(huì)是指針!), 但對(duì)于這個(gè)數(shù)組的大小, 我們可以進(jìn)行動(dòng)態(tài)分配
注意 :如果結(jié)構(gòu)體是通過(guò)calloc、malloc或 者new等動(dòng)態(tài)分配方式生成,在不需要時(shí)要釋放相應(yīng)的空間。
優(yōu)點(diǎn) :比起在結(jié)構(gòu)體中聲明一個(gè)指針變量、再進(jìn)行動(dòng)態(tài)分 配的辦法,這種方法效率要高。因?yàn)樵谠L問(wèn)數(shù)組內(nèi)容時(shí),不需要間接訪問(wèn),避免了兩次訪存。
缺點(diǎn) :在結(jié)構(gòu)體中,數(shù)組為0的數(shù)組必須在最后聲明,使 用上有一定限制。
對(duì)于編譯器而言, 數(shù)組名僅僅是一個(gè)符號(hào), 它不會(huì)占用任何空間, 它在結(jié)構(gòu)體中, 只是代表了一個(gè)偏移量, 代表一個(gè)不可修改的地址常量!
0長(zhǎng)度數(shù)組的用途
我們?cè)O(shè)想這樣一個(gè)場(chǎng)景, 我們?cè)诰W(wǎng)絡(luò)通信過(guò)程中使用的數(shù)據(jù)緩沖區(qū), 緩沖區(qū)包括一個(gè)len字段和data字段, 分別標(biāo)識(shí)數(shù)據(jù)的長(zhǎng)度和傳輸?shù)臄?shù)據(jù), 我們常見(jiàn)的有幾種設(shè)計(jì)思路:
- 定長(zhǎng)數(shù)據(jù)緩沖區(qū), 設(shè)置一個(gè)足夠大小 MAX_LENGTH 的數(shù)據(jù)緩沖區(qū)
- 設(shè)置一個(gè)指向?qū)嶋H數(shù)據(jù)的指針, 每次使用時(shí), 按照數(shù)據(jù)的長(zhǎng)度動(dòng)態(tài)的開(kāi)辟數(shù)據(jù)緩沖區(qū)的空間
我們從實(shí)際場(chǎng)景中應(yīng)用的設(shè)計(jì)來(lái)考慮他們的優(yōu)劣. 主要考慮的有, 緩沖區(qū)空間的開(kāi)辟, 釋放和訪問(wèn)。
1、定長(zhǎng)包(開(kāi)辟空間, 釋放, 訪問(wèn)):
比如我要發(fā)送 1024 字節(jié)的數(shù)據(jù), 如果用定長(zhǎng)包, 假設(shè)定長(zhǎng)包的長(zhǎng)度 MAX_LENGTH 為 2048, 就會(huì)浪費(fèi) 1024 個(gè)字節(jié)的空間, 也會(huì)造成不必要的流量浪費(fèi):
數(shù)據(jù)結(jié)構(gòu)定義:
// 定長(zhǎng)緩沖區(qū)
struct max_buffer
{
int len;
char data[MAX_LENGTH];
};
數(shù)據(jù)結(jié)構(gòu)大小:考慮對(duì)齊, 那么數(shù)據(jù)結(jié)構(gòu)的大小 >= sizeof(int) + sizeof(char) * MAX_LENGTH
由于考慮到數(shù)據(jù)的溢出, 變長(zhǎng)數(shù)據(jù)包中的 data 數(shù)組長(zhǎng)度一般會(huì)設(shè)置得足夠長(zhǎng)足以容納最大的數(shù)據(jù), 因此 max_buffer 中的 data 數(shù)組很多情況下都沒(méi)有填滿數(shù)據(jù), 因此造成了浪費(fèi)
數(shù)據(jù)包的構(gòu)造:假如我們要發(fā)送 CURR_LENGTH = 1024 個(gè)字節(jié), 我們?nèi)绾螛?gòu)造這個(gè)數(shù)據(jù)包呢;一般來(lái)說(shuō), 我們會(huì)返回一個(gè)指向緩沖區(qū)數(shù)據(jù)結(jié)構(gòu) max_buffer 的指針:
/// 開(kāi)辟
if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
mbuffer->len = CURR_LENGTH;
memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", mbuffer->len, mbuffer->data);
}
訪問(wèn):這段內(nèi)存要分兩部分使用;前部分 4 個(gè)字節(jié) p->len, 作為包頭(就是多出來(lái)的那部分),這個(gè)包頭是用來(lái)描述緊接著包頭后面的數(shù)據(jù)部分的長(zhǎng)度,這里是 1024, 所以前四個(gè)字節(jié)賦值為 1024 (既然我們要構(gòu)造不定長(zhǎng)數(shù)據(jù)包,那么這個(gè)包到底有多長(zhǎng)呢,因此,我們就必須通過(guò)一個(gè)變量來(lái)表明這個(gè)數(shù)據(jù)包的長(zhǎng)度,這就是len的作用);而緊接其后的內(nèi)存是真正的數(shù)據(jù)部分, 通過(guò) p->data, 最后, 進(jìn)行一個(gè) memcpy() 內(nèi)存拷貝, 把要發(fā)送的數(shù)據(jù)填入到這段內(nèi)存當(dāng)中
釋放:那么當(dāng)使用完畢釋放數(shù)據(jù)的空間的時(shí)候, 直接釋放就可以了
/// 銷毀
free(mbuffer);
mbuffer = NULL;
2、小結(jié):
使用定長(zhǎng)數(shù)組, 作為數(shù)據(jù)緩沖區(qū), 為了避免造成緩沖區(qū)溢出, 數(shù)組的大小一般設(shè)為足夠的空間 MAX_LENGTH, 而實(shí)際使用過(guò)程中, 達(dá)到 MAX_LENGTH 長(zhǎng)度的數(shù)據(jù)很少, 那么多數(shù)情況下, 緩沖區(qū)的大部分空間都是浪費(fèi)掉的
但是使用過(guò)程很簡(jiǎn)單, 數(shù)據(jù)空間的開(kāi)辟和釋放簡(jiǎn)單, 無(wú)需程序員考慮額外的操作
3、 指針數(shù)據(jù)包(開(kāi)辟空間, 釋放, 訪問(wèn)):
如果你將上面的長(zhǎng)度為 MAX_LENGTH 的定長(zhǎng)數(shù)組換為指針, 每次使用時(shí)動(dòng)態(tài)的開(kāi)辟 CURR_LENGTH 大小的空間, 那么就避免造成 MAX_LENGTH - CURR_LENGTH 空間的浪費(fèi), 只浪費(fèi)了一個(gè)指針域的空間:
數(shù)據(jù)包定義:
struct point_buffer
{
int len;
char *data;
};
數(shù)據(jù)結(jié)構(gòu)大?。嚎紤]對(duì)齊, 那么數(shù)據(jù)結(jié)構(gòu)的大小 >= sizeof(int) + sizeof(char *)
空間分配:但是也造成了使用在分配內(nèi)存時(shí),需采用兩步
// =====================
// 指針數(shù)組 占用-開(kāi)辟-銷毀
// =====================
/// 占用
printf("the length of struct test3:%d\n",sizeof(struct point_buffer));
/// 開(kāi)辟
if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
pbuffer->len = CURR_LENGTH;
if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
{
memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", pbuffer->len, pbuffer->data);
}
}首先, 需為結(jié)構(gòu)體分配一塊內(nèi)存空間;其次再為結(jié)構(gòu)體中的成員變量分配內(nèi)存空間。
這樣兩次分配的內(nèi)存是不連續(xù)的, 需要分別對(duì)其進(jìn)行管理. 當(dāng)使用長(zhǎng)度為的數(shù)組時(shí), 則是采用一次分配的原則, 一次性將所需的內(nèi)存全部分配給它。
釋放:相反, 釋放時(shí)也是一樣的:
/// 銷毀
free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;
小結(jié):
- 使用指針結(jié)果作為緩沖區(qū), 只多使用了一個(gè)指針大小的空間, 無(wú)需使用 MAX_LENGTH 長(zhǎng)度的數(shù)組, 不會(huì)造成空間的大量浪費(fèi)
- 但那是開(kāi)辟空間時(shí), 需要額外開(kāi)辟數(shù)據(jù)域的空間, 施放時(shí)候也需要顯示釋放數(shù)據(jù)域的空間, 但是實(shí)際使用過(guò)程中, 往往在函數(shù)中開(kāi)辟空間, 然后返回給使用者指向 struct point_buffer 的指針, 這時(shí)候我們并不能假定使用者了解我們開(kāi)辟的細(xì)節(jié), 并按照約定的操作釋放空間, 因此使用起來(lái)多有不便, 甚至造成內(nèi)存泄漏。
4、變長(zhǎng)數(shù)據(jù)緩沖區(qū)(開(kāi)辟空間, 釋放, 訪問(wèn))
定長(zhǎng)數(shù)組使用方便, 但是卻浪費(fèi)空間, 指針形式只多使用了一個(gè)指針的空間, 不會(huì)造成大量空間分浪費(fèi), 但是使用起來(lái)需要多次分配, 多次釋放, 那么有沒(méi)有一種實(shí)現(xiàn)方式能夠既不浪費(fèi)空間, 又使用方便的呢?
GNU C 的0長(zhǎng)度數(shù)組, 也叫變長(zhǎng)數(shù)組, 柔性數(shù)組就是這樣一個(gè)擴(kuò)展. 對(duì)于0長(zhǎng)數(shù)組的這個(gè)特點(diǎn),很容易構(gòu)造出變成結(jié)構(gòu)體,如緩沖區(qū),數(shù)據(jù)包等等:
數(shù)據(jù)結(jié)構(gòu)定義:
// 0長(zhǎng)度數(shù)組
struct zero_buffer
{
int len;
char data[0];
};
數(shù)據(jù)結(jié)構(gòu)大小:這樣的變長(zhǎng)數(shù)組常用于網(wǎng)絡(luò)通信中構(gòu)造不定長(zhǎng)數(shù)據(jù)包, 不會(huì)浪費(fèi)空間浪費(fèi)網(wǎng)絡(luò)流量, 因?yàn)閏har data[0]; 只是個(gè)數(shù)組名, 是不占用存儲(chǔ)空間的:
sizeof(struct zero_buffer) = sizeof(int)
開(kāi)辟空間:那么我們使用的時(shí)候, 只需要開(kāi)辟一次空間即可
/// 開(kāi)辟
if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
{
zbuffer->len = CURR_LENGTH;
memcpy(zbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", zbuffer->len, zbuffer->data);
}
釋放空間:釋放空間也是一樣的, 一次釋放即可
/// 銷毀
free(zbuffer);
zbuffer = NULL;
總結(jié):
// zero_length_array.c
#include <stdio.h>
#include <stdlib.h>
#define MAX_LENGTH 1024
#define CURR_LENGTH 512
// 0長(zhǎng)度數(shù)組
struct zero_buffer
{
int len;
char data[0];
}__attribute((packed));
// 定長(zhǎng)數(shù)組
struct max_buffer
{
int len;
char data[MAX_LENGTH];
}__attribute((packed));
// 指針數(shù)組
struct point_buffer
{
int len;
char *data;
}__attribute((packed));
int main(void)
{
struct zero_buffer *zbuffer = NULL;
struct max_buffer *mbuffer = NULL;
struct point_buffer *pbuffer = NULL;
// =====================
// 0長(zhǎng)度數(shù)組 占用-開(kāi)辟-銷毀
// =====================
/// 占用
printf("the length of struct test1:%d\n",sizeof(struct zero_buffer));
/// 開(kāi)辟
if ((zbuffer = (struct zero_buffer *)malloc(sizeof(struct zero_buffer) + sizeof(char) * CURR_LENGTH)) != NULL)
{
zbuffer->len = CURR_LENGTH;
memcpy(zbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", zbuffer->len, zbuffer->data);
}
/// 銷毀
free(zbuffer);
zbuffer = NULL;
// =====================
// 定長(zhǎng)數(shù)組 占用-開(kāi)辟-銷毀
// =====================
/// 占用
printf("the length of struct test2:%d\n",sizeof(struct max_buffer));
/// 開(kāi)辟
if ((mbuffer = (struct max_buffer *)malloc(sizeof(struct max_buffer))) != NULL)
{
mbuffer->len = CURR_LENGTH;
memcpy(mbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", mbuffer->len, mbuffer->data);
}
/// 銷毀
free(mbuffer);
mbuffer = NULL;
// =====================
// 指針數(shù)組 占用-開(kāi)辟-銷毀
// =====================
/// 占用
printf("the length of struct test3:%d\n",sizeof(struct point_buffer));
/// 開(kāi)辟
if ((pbuffer = (struct point_buffer *)malloc(sizeof(struct point_buffer))) != NULL)
{
pbuffer->len = CURR_LENGTH;
if ((pbuffer->data = (char *)malloc(sizeof(char) * CURR_LENGTH)) != NULL)
{
memcpy(pbuffer->data, "Hello World", CURR_LENGTH);
printf("%d, %s\n", pbuffer->len, pbuffer->data);
}
}
/// 銷毀
free(pbuffer->data);
free(pbuffer);
pbuffer = NULL;
return EXIT_SUCCESS;
}GNU Document中 變長(zhǎng)數(shù)組的支持
參考:
6.17 Arrays of Length Zero
C Struct Hack – Structure with variable length array
在 C90 之前, 并不支持0長(zhǎng)度的數(shù)組, 0長(zhǎng)度數(shù)組是 GNU C 的一個(gè)擴(kuò)展, 因此早期的編譯器中是無(wú)法通過(guò)編譯的;對(duì)于 GNU C 增加的擴(kuò)展, GCC 提供了編譯選項(xiàng)來(lái)明確的標(biāo)識(shí)出他們:
- -pedantic 選項(xiàng),那么使用了擴(kuò)展語(yǔ)法的地方將產(chǎn)生相應(yīng)的警告信息
- -Wall 使用它能夠使GCC產(chǎn)生盡可能多的警告信息
- -Werror, 它要求GCC將所有的警告當(dāng)成錯(cuò)誤進(jìn)行處理
// 1.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char a[0];
printf("%ld", sizeof(a));
return EXIT_SUCCESS;
}
我們來(lái)編譯:
gcc 1.c -Wall # 顯示所有警告
#none warning and error
gcc 1.c -Wall -pedantic # 對(duì)GNU C的擴(kuò)展顯示警告
1.c: In function ‘main’:
1.c:7: warning: ISO C forbids zero-size array ‘a’
gcc 1.c -Werror -Wall -pedantic # 顯示所有警告同時(shí)GNU C的擴(kuò)展顯示警告, 將警告用error顯示
cc1: warnings being treated as errors
1.c: In function ‘main’:
1.c:7: error: ISO C forbids zero-size array ‘a’

0長(zhǎng)度數(shù)組其實(shí)就是靈活的運(yùn)用的數(shù)組指向的是其后面的連續(xù)的內(nèi)存空間:
struct buffer
{
int len;
char data[0];
};
在早期沒(méi)引入0長(zhǎng)度數(shù)組的時(shí)候, 大家是通過(guò)定長(zhǎng)數(shù)組和指針的方式來(lái)解決的, 但是:
定長(zhǎng)數(shù)組定義了一個(gè)足夠大的緩沖區(qū), 這樣使用方便, 但是每次都造成空間的浪費(fèi)
指針的方式, 要求程序員在釋放空間是必須進(jìn)行多次的free操作, 而我們?cè)谑褂玫倪^(guò)程中往往在函數(shù)中返回了指向緩沖區(qū)的指針, 我們并不能保證每個(gè)人都理解并遵從我們的釋放方式
所以 GNU 就對(duì)其進(jìn)行了0長(zhǎng)度數(shù)組的擴(kuò)展. 當(dāng)使用data[0]的時(shí)候, 也就是0長(zhǎng)度數(shù)組的時(shí)候,0長(zhǎng)度數(shù)組作為數(shù)組名, 并不占用存儲(chǔ)空間.
在C99之后,也加了類似的擴(kuò)展,只不過(guò)用的是 char payload[]這種形式(所以如果你在編譯的時(shí)候確實(shí)需要用到-pedantic參數(shù),那么你可以將char payload[0]類型改成char payload[], 這樣就可以編譯通過(guò)了,當(dāng)然你的編譯器必須支持C99標(biāo)準(zhǔn)的,如果太古老的編譯器,那可能不支持了)
// 2.c payload
#include <stdio.h>
#include <stdlib.h>
struct payload
{
int len;
char data[];
};
int main(void)
{
struct payload pay;
printf("%ld", sizeof(pay));
return EXIT_SUCCESS;
}使用 -pedantic 編譯后, 不出現(xiàn)警告, 說(shuō)明這種語(yǔ)法是 C 標(biāo)準(zhǔn)的
gcc 2.c -pedantic -std=c99

所以結(jié)構(gòu)體的末尾, 就是指向了其后面的內(nèi)存數(shù)據(jù)。因此我們可以很好的將該類型的結(jié)構(gòu)體作為數(shù)據(jù)報(bào)文的頭格式,并且最后一個(gè)成員變量,也就剛好是數(shù)據(jù)內(nèi)容了.
GNU手冊(cè)還提供了另外兩個(gè)結(jié)構(gòu)體來(lái)說(shuō)明,更容易看懂意思:
struct f1 {
int x;
int y[];
} f1 = { 1, { 2, 3, 4 } };
struct f2 {
struct f1 f1;
int data[3];
} f2 = { { 1 }, { 5, 6, 7 } };
我把f2里面的2,3,4改成了5,6,7以示區(qū)分。如果你把數(shù)據(jù)打出來(lái)。即如下的信息:
f1.x = 1
f1.y[0] = 2
f1.y[1] = 3
f1.y[2] = 4
也就是f1.y指向的是{2,3,4}這塊內(nèi)存中的數(shù)據(jù)。所以我們就可以輕易的得到,f2.f1.y指向的數(shù)據(jù)也就是正好f2.data的內(nèi)容了。打印出來(lái)的數(shù)據(jù):
f2.f1.x = 1
f2.f1.y[0] = 5
f2.f1.y[1] = 6
f2.f1.y[2] = 7
如果你不是很確認(rèn)其是否占用空間. 你可以用sizeof來(lái)計(jì)算一下。就可以知道sizeof(struct f1)=4,也就是int y[]其實(shí)是不占用空間的。但是這個(gè)0長(zhǎng)度的數(shù)組,必須放在結(jié)構(gòu)體的末尾。如果你沒(méi)有把它放在末尾的話。編譯的時(shí)候,會(huì)有如下的錯(cuò)誤:
main.c:37:9: error: flexible array member not at end of struct
int y[];
^
到這邊,你可能會(huì)有疑問(wèn),如果將struct f1中的int y[]替換成int *y,又會(huì)是如何?這就涉及到數(shù)組和指針的問(wèn)題了. 有時(shí)候吧,這兩個(gè)是一樣的,有時(shí)候又有區(qū)別。
首先要說(shuō)明的是,支持0長(zhǎng)度數(shù)組的擴(kuò)展,重點(diǎn)在數(shù)組,也就是不能用int *y指針來(lái)替換。sizeof的長(zhǎng)度就不一樣了。把struct f1改成這樣:
struct f3 {
int x;
int *y;
};
在32/64位下, int均是4個(gè)字節(jié), sizeof(struct f1)=4,而sizeof(struct f3)=16
因?yàn)?int *y 是指針, 指針在64位下, 是64位的, sizeof(struct f3) = 16, 如果在32位環(huán)境的話, sizeof(struct f3) 則是 8 了, sizeof(struct f1) 不變. 所以 int *y 是不能替代 int y[] 的;
代碼如下:
// 3.c
#include <stdio.h>
#include <stdlib.h>
struct f1 {
int x;
int y[];
} f1 = { 1, { 2, 3, 4 } };
struct f2 {
struct f1 f1;
int data[3];
} f2 = { { 1 }, { 5, 6, 7 } };
struct f3
{
int x;
int *y;
};
int main(void)
{
printf("sizeof(f1) = %d\n", sizeof(struct f1));
printf("sizeof(f2) = %d\n", sizeof(struct f2));
printf("szieof(f3) = %d\n\n", sizeof(struct f3));
printf("f1.x = %d\n", f1.x);
printf("f1.y[0] = %d\n", f1.y[0]);
printf("f1.y[1] = %d\n", f1.y[1]);
printf("f1.y[2] = %d\n", f1.y[2]);
printf("f2.f1.x = %d\n", f1.x);
printf("f2.f1.y[0] = %d\n", f2.f1.y[0]);
printf("f2.f1.y[1] = %d\n", f2.f1.y[1]);
printf("f2.f1.y[2] = %d\n", f2.f1.y[2]);
return EXIT_SUCCESS;
}
0長(zhǎng)度數(shù)組的其他特征
1、為什么0長(zhǎng)度數(shù)組不占用存儲(chǔ)空間:
0長(zhǎng)度數(shù)組與指針實(shí)現(xiàn)有什么區(qū)別呢, 為什么0長(zhǎng)度數(shù)組不占用存儲(chǔ)空間呢?
其實(shí)本質(zhì)上涉及到的是一個(gè)C語(yǔ)言里面的數(shù)組和指針的區(qū)別問(wèn)題. char a[1]里面的a和char *b的b相同嗎?
《 Programming Abstractions in C》(Roberts, E. S.,機(jī)械工業(yè)出版社,2004.6)82頁(yè)里面說(shuō):
“arr is defined to be identical to &arr[0]”.
也就是說(shuō),char a[1]里面的a實(shí)際是一個(gè)常量,等于&a[0]。而char *b是有一個(gè)實(shí)實(shí)在在的指針變量b存在。所以,a=b是不允許的,而b=a是允許的。兩種變量都支持下標(biāo)式的訪問(wèn),那么對(duì)于a[0]和b[0]本質(zhì)上是否有區(qū)別?我們可以通過(guò)一個(gè)例子來(lái)說(shuō)明。
參見(jiàn)如下兩個(gè)程序 gdb_zero_length_array.c 和 gdb_zero_length_array.c:
// gdb_zero_length_array.c
#include <stdio.h>
#include <stdlib.h>
struct str
{
int len;
char s[0];
};
struct foo
{
struct str *a;
};
int main(void)
{
struct foo f = { NULL };
printf("sizeof(struct str) = %d\n", sizeof(struct str));
printf("before f.a->s.\n");
if(f.a->s)
{
printf("before printf f.a->s.\n");
printf(f.a->s);
printf("before printf f.a->s.\n");
}
return EXIT_SUCCESS;
}
// gdb_pzero_length_array.c
#include <stdio.h>
#include <stdlib.h>
struct str
{
int len;
char *s;
};
struct foo
{
struct str *a;
};
int main(void)
{
struct foo f = { NULL };
printf("sizeof(struct str) = %d\n", sizeof(struct str));
printf("before f.a->s.\n");
if (f.a->s)
{
printf("before printf f.a->s.\n");
printf(f.a->s);
printf("before printf f.a->s.\n");
}
return EXIT_SUCCESS;
}
可以看到這兩個(gè)程序雖然都存在訪問(wèn)異常, 但是段錯(cuò)誤的位置卻不同
我們將兩個(gè)程序編譯成匯編, 然戶 diff 查看他們的匯編代碼有何不同
gcc -S gdb_zero_length_array.c -o gdb_test.s
gcc -S gdb_pzero_length_array.c -o gdb_ptest
diff gdb_test.s gdb_ptest.s
1c1
< .file "gdb_zero_length_array.c"
---
> .file "gdb_pzero_length_array.c"
23c23
< movl $4, %esi
---
> movl $16, %esi
30c30
< addq $4, %rax
---
> movq 8(%rax), %rax
36c36
< addq $4, %rax
---
> movq 8(%rax), %rax
# printf("sizeof(struct str) = %d\n", sizeof(struct str));
23c23
< movl $4, %esi #printf("sizeof(struct str) = %d\n", sizeof(struct str));
---
> movl $16, %esi #printf("sizeof(struct str) = %d\n", sizeof(struct str));
從64位系統(tǒng)中, 匯編我們看出, 變長(zhǎng)數(shù)組結(jié)構(gòu)的大小為4, 而指針形式的結(jié)構(gòu)大小為16:
f.a->s
30c30/36c36
< addq $4, %rax
---
> movq 8(%rax), %rax
可以看到有:
- 對(duì)于 char s[0] 來(lái)說(shuō), 匯編代碼用了 addq 指令, addq $4, %rax
- 對(duì)于 char*s 來(lái)說(shuō),匯編代碼用了 movq 指令, movq 8(%rax), %rax
addq 對(duì) %rax + sizeof(struct str), 即str結(jié)構(gòu)的末尾即是char s[0]的地址, 這一步只是拿到了其地址, 而 movq 則是把地址里的內(nèi)容放進(jìn)去, 因此有時(shí)也被翻譯為leap指令, 參見(jiàn)下一列子
從這里可以看到, 訪問(wèn)成員數(shù)組名其實(shí)得到的是數(shù)組的相對(duì)地址, 而訪問(wèn)成員指針其實(shí)是相對(duì)地址里的內(nèi)容(這和訪問(wèn)其它非指針或數(shù)組的變量是一樣的):
訪問(wèn)相對(duì)地址,程序不會(huì)crash,但是,訪問(wèn)一個(gè)非法的地址中的內(nèi)容,程序就會(huì)crash。
// 4-1.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *a;
printf("%p\n", a);
return EXIT_SUCCESS;
}
//4-2.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char a[0];
printf("%p\n", a);
return EXIT_SUCCESS;
}
對(duì)于 char a[0] 來(lái)說(shuō), 匯編代碼用了 leal 指令, leal 16(%esp), %eax:
對(duì)于 char *a 來(lái)說(shuō),匯編代碼用了 movl 指令, movl 28(%esp), %eax
2、地址優(yōu)化:
// 5-1.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char a[0];
printf("%p\n", a);
char b[0];
printf("%p\n", b);
return EXIT_SUCCESS;
}
由于0長(zhǎng)度數(shù)組是 GNU C 的擴(kuò)展, 不被標(biāo)準(zhǔn)庫(kù)任可, 那么一些巧妙編寫的詭異代碼, 其執(zhí)行結(jié)果就是依賴于編譯器和優(yōu)化策略的實(shí)現(xiàn)的.
比如上面的代碼, a和b的地址就會(huì)被編譯器優(yōu)化到一處, 因?yàn)閍[0] 和 b[0] 對(duì)于程序來(lái)說(shuō)是無(wú)法使用的, 這讓我們想到了什么?
編譯器對(duì)于相同字符串常量, 往往地址也是優(yōu)化到一處, 減少空間占用:
// 5-2.c
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
const char *a = "Hello";
printf("%p\n", a);
const char *b = "Hello";
printf("%p\n", b);
const char c[] = "Hello";
printf("%p\n", c);
return EXIT_SUCCESS;
}
到此這篇關(guān)于一文帶你了解C語(yǔ)言中的0長(zhǎng)度數(shù)組(可變數(shù)組/柔性數(shù)組)的文章就介紹到這了,更多相關(guān)C語(yǔ)言0長(zhǎng)度數(shù)組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實(shí)踐分?jǐn)?shù)類中運(yùn)算符重載的方法參考
今天小編就為大家分享一篇關(guān)于C++實(shí)踐分?jǐn)?shù)類中運(yùn)算符重載的方法參考,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
C++標(biāo)準(zhǔn)模版庫(kù)(STL)之vector容器詳解
vector的功能和水桶一樣,就是用來(lái)裝東西的,并且vector還提供了迭代器來(lái)很方便的訪問(wèn)這些數(shù)據(jù),下面就讓我們一起看下如何使用C++的vector吧2023-03-03
C++應(yīng)用Eigen庫(kù)對(duì)應(yīng)實(shí)現(xiàn)matlab中部分函數(shù)問(wèn)題
這篇文章主要介紹了C++應(yīng)用Eigen庫(kù)對(duì)應(yīng)實(shí)現(xiàn)matlab中部分函數(shù)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12
關(guān)于C語(yǔ)言中數(shù)據(jù)在內(nèi)存中的存儲(chǔ)詳解
這篇文章主要給大家介紹了關(guān)于C語(yǔ)言中數(shù)據(jù)在內(nèi)存中的存儲(chǔ)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04

