C語(yǔ)言中0數(shù)組\柔性數(shù)組的使用詳解
前言:
上次看到一篇面試分享,里面有個(gè)朋友說(shuō),面試官問(wèn)了char[0] 相關(guān)問(wèn)題,但是自己沒(méi)有遇到過(guò),就繞過(guò)了這個(gè)問(wèn)題。
我自己在這篇文章下面做了一些回復(fù)。
現(xiàn)在我想結(jié)合我自己的理解,解釋一下這個(gè) char[0] C語(yǔ)言柔性數(shù)組的問(wèn)題。
0數(shù)組和柔性數(shù)組的介紹
0數(shù)組顧名思義,就是數(shù)組長(zhǎng)度定義為0,我們一般知道數(shù)組長(zhǎng)度定義至少為1才會(huì)給它分配實(shí)際的空間,而定義了0的數(shù)組是沒(méi)有任何空間,但是如果像上面的結(jié)構(gòu)體一樣在最后一個(gè)成員定義為零數(shù)組,雖然零數(shù)組沒(méi)有分配的空間,但是它可以當(dāng)作一個(gè)偏移量,因?yàn)閿?shù)組名這個(gè)符號(hào)本身代表了一個(gè)不可修改的地址常量。柔性數(shù)組也叫可伸縮性數(shù)組,而0數(shù)組是一種柔性數(shù)組。
因?yàn)樵谠缙跊](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ǔ)空間。這樣就可以更加高效的利用內(nèi)存。
在C99之后,也加了類(lèi)似的擴(kuò)展,只不過(guò)用的是 char payload[]這種形式(所以如果你在編譯的時(shí)候確實(shí)需要用到-pedantic參數(shù),那么你可以將char payload[0]類(lèi)型改成char payload[], 這樣就可以編譯通過(guò)了,當(dāng)然你的編譯器必須支持C99標(biāo)準(zhǔn)的,如果太古老的編譯器,那可能不支持了。
0數(shù)組的常規(guī)使用
首先我們定義一個(gè)結(jié)構(gòu)體,再在一個(gè)結(jié)構(gòu)體的最后,定義一個(gè)長(zhǎng)度為0的數(shù)組,就可以使得這個(gè)結(jié)構(gòu)體是可變長(zhǎng)的。
如下所示:
// 0長(zhǎng)度數(shù)組 struct zero_buffer { int len; char data[0]; };
這個(gè)時(shí)候 data[0] 只是個(gè)數(shù)組名, 是不占用存儲(chǔ)空間的.
這個(gè)結(jié)構(gòu)體的大小用sizeof取長(zhǎng)度,實(shí)際就是它的成員int的長(zhǎng)度,data[0]不占用空間。(數(shù)組名僅僅是一個(gè)符號(hào), 它不會(huì)占用任何空間, 它在結(jié)構(gòu)體中, 只是代表了一個(gè)偏移量, 代表一個(gè)不可修改的地址常量!)
sizeof(struct zero_buffer) = sizeof(int)
printf("zero struct length is:%d int length is:%d\n",sizeof(struct zero_buffer),sizeof(int));
zero struct length is:4 int length is:4
對(duì)于0長(zhǎng)數(shù)組的這個(gè)特點(diǎn),很容易構(gòu)造出我們需要的數(shù)據(jù)結(jié)構(gòu),如緩沖區(qū),數(shù)據(jù)包等等。
結(jié)構(gòu)體定義如上所示
假設(shè)我們需要設(shè)置一條tcp待發(fā)送的數(shù)據(jù),長(zhǎng)度是15,數(shù)據(jù)內(nèi)容是"Hello My Friend",這樣我們就可以按照如下去定義了。其中 zbuffer->data 為定義數(shù)據(jù)的地址,len表示數(shù)據(jù)的長(zhǎng)度。
開(kāi)辟空間之后使用
我們使用的時(shí)候, 只需要開(kāi)辟一次空間即可。
#define CURR_LENGTH 15struct zero_buffer *zbuffer = NULL;// 開(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 My Friend", CURR_LENGTH); printf("%d, %s\n", zbuffer->len, zbuffer->data);}
使用完釋放空間
釋放空間一次釋放即可
// 銷(xiāo)毀 free(zbuffer); zero_buffer = NULL;
其他方法實(shí)現(xiàn)一些不定長(zhǎng)數(shù)據(jù)的傳輸
除了0數(shù)組之外,還有使用定長(zhǎng)數(shù)組和指針數(shù)組實(shí)現(xiàn)柔性數(shù)組的功能。
定長(zhǎng)數(shù)組
定長(zhǎng)數(shù)組顧名思義,就是在結(jié)構(gòu)體里面有個(gè)定長(zhǎng)的數(shù)組,這個(gè)數(shù)組大小是按照我們定義數(shù)據(jù)最大來(lái)進(jìn)行設(shè)置的,為了就是防止數(shù)據(jù)儲(chǔ)存的時(shí)候溢出。
定義
// 定長(zhǎng)緩沖區(qū) #define MAX_LENGTH 512 struct max_buffer { int len; char data[MAX_LENGTH]; };
不過(guò)使用過(guò)程中,比如我要發(fā)送 512 字節(jié)的數(shù)據(jù), 如果用定長(zhǎng)包, 假設(shè)定長(zhǎng)包的最大長(zhǎng)度 MAX_LENGTH 為 1024, 那么就會(huì)浪費(fèi) 512 個(gè)字節(jié)的空間, 也會(huì)造成不必要的流量浪費(fèi)。如果數(shù)組結(jié)構(gòu)對(duì)齊放置(這塊知識(shí)詳細(xì)可以看我之前的數(shù)據(jù)對(duì)齊的文章) sizeof(struct max_buffer) = sizeof(int)+ sizieof(char) * MAX_LENGTH
數(shù)據(jù)包的構(gòu)造
一般來(lái)說(shuō), 我們會(huì)返回一個(gè)指向緩沖區(qū)數(shù)據(jù)結(jié)構(gòu) max_buffer 的指針.
#define CURR_LENGTH 512struct max_buffer *mbuffer = NULL;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);} 作者:良知猶存 鏈接:https://juejin.cn/post/6960470520831672333 來(lái)源:掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
前部分 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;
使用定長(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ú)需程序員考慮額外的操作
指針數(shù)組
它和0數(shù)組的區(qū)別在于,零數(shù)組最后一個(gè)結(jié)構(gòu)體元素定義一個(gè)data[0],而指針數(shù)組就是結(jié)構(gòu)體中需要定義一個(gè)指針數(shù)組,這里面的指針數(shù)組不需要特定在結(jié)構(gòu)體的最后一個(gè)元素。
struct point_buffer { char *data; int len; };
考慮數(shù)組結(jié)構(gòu)對(duì)齊(這塊知識(shí)詳細(xì)可以看我之前的[數(shù)據(jù)對(duì)齊](http://www.dbjr.com.cn/article/211811.htm)的文章), 那么數(shù)據(jù)結(jié)構(gòu)的大小 sizeof(point_buffer)= sizeof(int) + (補(bǔ)齊int與char * 類(lèi)型的長(zhǎng)度值)+ sizeof(char * ),在我的64位編譯環(huán)境中int類(lèi)型是4byte,char * 類(lèi)型為8byte,所以補(bǔ)齊的長(zhǎng)度為8-4,最終sizeof(point_buffer) 為16byte。
如果結(jié)構(gòu)體加上 _attribute((packed)) 數(shù)據(jù)對(duì)齊修飾,則 sizeof(point_buffer)= sizeof(int) sizeof(char * ),最終計(jì)算為12byte。
空間分配使用
#define CURR_LENGTH 1024 struct point_buffer *pbuffer = NULL;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); }}
分配內(nèi)存時(shí),需采用兩步
首先, 需為結(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é)果作為緩沖區(qū), 只多使用了一個(gè)指針大小的空間, 無(wú)需使用固定長(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)存泄漏
小結(jié):
定長(zhǎng)數(shù)組使用方便, 但是卻浪費(fèi)空間, 指針形式只多使用了一個(gè)指針的空間, 不會(huì)造成大量空間分浪費(fèi), 但是使用起來(lái)需要多次分配, 多次釋放。所以最優(yōu)解
0數(shù)組的優(yōu)劣以及注意事項(xià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),避免了兩次訪存。此外,0數(shù)組也不會(huì)像定長(zhǎng)數(shù)組會(huì)造成一定的內(nèi)存的浪費(fèi)。
缺點(diǎn) :在結(jié)構(gòu)體中,數(shù)組為0的數(shù)組必須在最后聲明,使用上有一定限制。
總結(jié)
到此這篇關(guān)于C語(yǔ)言中0數(shù)組\柔性數(shù)組使用的文章就介紹到這了,更多相關(guān)C語(yǔ)言0數(shù)組\柔性數(shù)組內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言動(dòng)態(tài)內(nèi)存函數(shù)(malloc、calloc、realloc、free)詳解
在C語(yǔ)言中,動(dòng)態(tài)內(nèi)存函數(shù)是塊重要的知識(shí)點(diǎn),以往,我們開(kāi)辟空間都是固定得,數(shù)組編譯結(jié)束后就不能繼續(xù)給它開(kāi)辟空間了,開(kāi)辟的空間滿了,就不能在開(kāi)辟空間了,學(xué)習(xí)本文章,我們就可以解決這個(gè)問(wèn)題,向內(nèi)存申請(qǐng)空間,感興趣的小伙伴跟著小編一起來(lái)看看吧2023-08-08C++?RBTree紅黑樹(shù)的性質(zhì)與實(shí)現(xiàn)
紅黑樹(shù)是一種二叉搜索樹(shù),但在每個(gè)結(jié)點(diǎn)上增加一個(gè)存儲(chǔ)位表示結(jié)點(diǎn)的顏色,可以是Red或Black;通過(guò)對(duì)任何一條從根到葉子的路徑上各個(gè)結(jié)點(diǎn)著色方式的限制,紅黑樹(shù)確保沒(méi)有一條路徑會(huì)比其他路徑長(zhǎng)出倆倍,因而是平衡的2023-03-03C語(yǔ)言實(shí)現(xiàn)關(guān)機(jī)小程序
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)關(guān)機(jī)小程序,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02C語(yǔ)言容易被忽視的函數(shù)設(shè)計(jì)原則基礎(chǔ)
C語(yǔ)言的設(shè)計(jì)目標(biāo)是提供一種能以簡(jiǎn)易的方式編譯、處理低級(jí)存儲(chǔ)器、產(chǎn)生少量的機(jī)器碼以及不需要任何運(yùn)行環(huán)境支持便能運(yùn)行的編程語(yǔ)言.那么C語(yǔ)言函數(shù)設(shè)計(jì)的一般原則和技巧都是怎樣的呢,下面帶你了解2022-04-04基于C++實(shí)現(xiàn)日期計(jì)算器的詳細(xì)教程
在現(xiàn)代社會(huì)中,計(jì)算器已經(jīng)進(jìn)入了每一個(gè)家庭,人們?cè)谏詈蛯W(xué)習(xí)中經(jīng)常需要使用到計(jì)算器,下面這篇文章主要給大家介紹了關(guān)于基于C++實(shí)現(xiàn)日期計(jì)算器的相關(guān)資料,需要的朋友可以參考下2022-06-06HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用分析詳解
本篇文章是對(duì)HDOJ 1443 約瑟夫環(huán)的最新應(yīng)用進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05