嵌入式C語(yǔ)言自我修養(yǎng)05:零長(zhǎng)度數(shù)組

5.1 什么是零長(zhǎng)度數(shù)組

顧名思義,零長(zhǎng)度數(shù)組就是長(zhǎng)度為0的數(shù)組。

在羅江等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專(zhuān)注、極致的服務(wù)理念,為客戶(hù)提供成都網(wǎng)站制作、成都做網(wǎng)站、外貿(mào)營(yíng)銷(xiāo)網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作按需求定制制作,公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),高端網(wǎng)站設(shè)計(jì),成都全網(wǎng)營(yíng)銷(xiāo),外貿(mào)營(yíng)銷(xiāo)網(wǎng)站建設(shè),羅江網(wǎng)站建設(shè)費(fèi)用合理。

ANSI C 標(biāo)準(zhǔn)規(guī)定:定義一個(gè)數(shù)組時(shí),數(shù)組的長(zhǎng)度必須是一個(gè)常數(shù),即數(shù)組的長(zhǎng)度在編譯的時(shí)候是確定的。在ANSI C 中定義一個(gè)數(shù)組的方法如下:

int  a[10];

C99 新標(biāo)準(zhǔn)規(guī)定:可以定義一個(gè)變長(zhǎng)數(shù)組。

int len;
int a[len];

也就是說(shuō),數(shù)組的長(zhǎng)度在編譯時(shí)是未確定的,在程序運(yùn)行的時(shí)候才確定,甚至可以由用戶(hù)指定大小。比如,我們可以定義一個(gè)數(shù)組,然后在程序運(yùn)行時(shí)才指定這個(gè)數(shù)組的大小,還可以通過(guò)輸入數(shù)據(jù)來(lái)初始化數(shù)組,示例代碼如下。

int main(void)
{
    int len;

    printf("input array len:");
    scanf("%d",&len);
    int a[len];

    for(int i=0;i<len;i++)
    {
        printf("a[%d]= ",i);
        scanf("%d",&a[i]);
    }

      printf("a array print:\n");
    for(int i=0;i<len;i++)
        printf("a[%d] = %d\n",i,a[i]);

    return 0;
}

在這個(gè)程序中,我們定義一個(gè)變量 len,作為數(shù)組的長(zhǎng)度。程序運(yùn)行后,我們可以通過(guò)輸入指定數(shù)組的長(zhǎng)度并初始化,最后再將數(shù)組的元素打印出來(lái)。程序的運(yùn)行結(jié)果如下:

input array len:3
a[0]= 6
a[1]= 7
a[2]= 8
a  array print:
a[0] = 6
a[1] = 7
a[2] = 8

GNU C 可能覺(jué)得變長(zhǎng)數(shù)組還不過(guò)癮,再來(lái)一個(gè)實(shí)錘:支持零長(zhǎng)度數(shù)組。這下沒(méi)有其它編譯器比我狠吧!是的,如果我們?cè)诔绦蛑卸x一個(gè)零長(zhǎng)度數(shù)組,你會(huì)發(fā)現(xiàn)除了 GCC 編譯器,在其它編譯環(huán)境下可能就編譯通不過(guò)或者有警告信息。零長(zhǎng)度數(shù)組的定義如下:

int a[0];

零長(zhǎng)度數(shù)組有一個(gè)奇特的地方,就是它不占用內(nèi)存存儲(chǔ)空間。我們使用 sizeof 關(guān)鍵字來(lái)查看一下零長(zhǎng)度數(shù)組在內(nèi)存中所占存儲(chǔ)空間的大小,代碼如下。

int buffer[0];
int main(void)
{
    printf("%d\n", sizeof(buffer));
    return 0;
}

在這個(gè)程序中,我們定義一個(gè)零長(zhǎng)度數(shù)組,使用 sizeof 查看其大小可以看到:零長(zhǎng)度數(shù)組在內(nèi)存中不占用空間,大小為0。

零長(zhǎng)度數(shù)組一般單獨(dú)使用的機(jī)會(huì)很少,它常常作為結(jié)構(gòu)體的一個(gè)成員,構(gòu)成一個(gè)變長(zhǎng)結(jié)構(gòu)體。

struct buffer{
    int len;
    int a[0];
};
int main(void)
{
      printf("%d\n",sizeof(struct buffer));
      return 0;
}

零長(zhǎng)度數(shù)組在結(jié)構(gòu)體中同樣不占用存儲(chǔ)空間,所以 buffer 結(jié)構(gòu)體的大小為4。

5.2 零長(zhǎng)度數(shù)組使用示例

零長(zhǎng)度數(shù)組經(jīng)常以變長(zhǎng)結(jié)構(gòu)體的形式,在某些特殊的應(yīng)用場(chǎng)合,被程序員使用。在一個(gè)變長(zhǎng)結(jié)構(gòu)體中,零長(zhǎng)度數(shù)組不占用結(jié)構(gòu)體的存儲(chǔ)空間,但是我們可以通過(guò)使用結(jié)構(gòu)體的成員 a 去訪問(wèn)內(nèi)存,非常方便。變長(zhǎng)結(jié)構(gòu)體的使用示例如下。

struct buffer{
    int len;
    int a[0];
};
int main(void)
{
    struct buffer *buf;
    buf = (struct buffer *)malloc \
        (sizeof(struct buffer)+ 20);

    buf->len = 20;
    strcpy(buf->a, "hello wanglitao!\n");
    puts(buf->a);

    free(buf);  
    return 0;
}

在這個(gè)程序中,我們使用 malloc 申請(qǐng)一片內(nèi)存,大小為 sizeof(buffer) + 20,即24個(gè)字節(jié)大小。其中4個(gè)字節(jié)用來(lái)存儲(chǔ)結(jié)構(gòu)體指針 buf 指向的結(jié)構(gòu)體類(lèi)型變量,另外20個(gè)字節(jié)空間,才是我們真正使用的內(nèi)存空間。我們可以通過(guò)結(jié)構(gòu)體成員 a,直接訪問(wèn)這片內(nèi)存。

通過(guò)這種靈活的動(dòng)態(tài)內(nèi)存申請(qǐng)方式,這個(gè) buffer 結(jié)構(gòu)體表示的一片內(nèi)存緩沖區(qū),就可以隨時(shí)調(diào)整,可大可小。這個(gè)特性,在一些場(chǎng)合非常有用。比如,現(xiàn)在很多在線視頻網(wǎng)站,都支持多種格式的視頻播放:普清、高清、超清、1080P、藍(lán)光甚至4K。如果我們本地程序需要在內(nèi)存中申請(qǐng)一個(gè) buffer 用來(lái)緩存解碼后的視頻數(shù)據(jù),那么,不同的播放格式,需要的 buffer 大小是不一樣的。如果我們按照 4K 的標(biāo)準(zhǔn)去申請(qǐng)內(nèi)存,那么當(dāng)播放普清視頻時(shí),就用不了這么大的緩沖區(qū),白白浪費(fèi)內(nèi)存。而使用變長(zhǎng)結(jié)構(gòu)體,我們就可以根據(jù)用戶(hù)的播放格式設(shè)置,靈活地申請(qǐng)不同大小的 buffer,大大節(jié)省了內(nèi)存空間。

5.3 零長(zhǎng)度數(shù)組在內(nèi)核中的使用

零長(zhǎng)度數(shù)組在內(nèi)核中,一般以變長(zhǎng)結(jié)構(gòu)體的形式使用。今天我們就分析一下 Linux 內(nèi)核中的 USB 驅(qū)動(dòng)。在網(wǎng)卡驅(qū)動(dòng)中,大家可能都比較熟悉一個(gè)名字:套接字緩沖區(qū),即 socket buffer,用來(lái)傳輸網(wǎng)絡(luò)數(shù)據(jù)包。同樣,在 USB 驅(qū)動(dòng)中,也有一個(gè)類(lèi)似的東西,叫 URB,其全名為 USB request block,即 USB 請(qǐng)求塊,用來(lái)傳輸 USB 數(shù)據(jù)包。

struct urb {
    struct kref kref;
    void *hcpriv;
    atomic_t use_count;
    atomic_t reject;
    int unlinked;

    struct list_head urb_list;
    struct list_head anchor_list;
    struct usb_anchor *anchor;
    struct usb_device *dev;
    struct usb_host_endpoint *ep;
    unsigned int pipe;
    unsigned int stream_id;
    int status;
    unsigned int transfer_flags;
    void *transfer_buffer;
    dma_addr_t transfer_dma;
    struct scatterlist *sg;
    int num_mapped_sgs;
    int num_sgs;
    u32 transfer_buffer_length;
    u32 actual_length;
    unsigned char *setup_packet;
    dma_addr_t setup_dma;
    int start_frame;
    int number_of_packets;
    int interval;

    int error_count;
    void *context;
    usb_complete_t complete;
    struct usb_iso_packet_descriptor iso_frame_desc[0];
};

在這個(gè)結(jié)構(gòu)體內(nèi)定義了 USB 數(shù)據(jù)包的傳輸方向、傳輸?shù)刂?、傳輸大小、傳輸模式等。這些細(xì)節(jié)我們不深究,我們只看最后一個(gè)成員:

struct usb_iso_packet_descriptor iso_frame_desc[0];

在 URB 結(jié)構(gòu)體的最后,定義一個(gè)零長(zhǎng)度數(shù)組,主要用于 USB 的同步傳輸。USB 有4種傳輸模式:中斷傳輸、控制傳輸、批量傳輸和同步傳輸。不同的 USB 設(shè)備對(duì)傳輸速度、傳輸數(shù)據(jù)安全性的要求不同,所采用的傳輸模式是不同的。USB 攝像頭對(duì)視頻或圖像的傳輸實(shí)時(shí)性要求較高,對(duì)數(shù)據(jù)的丟幀不是很在意,丟一幀無(wú)所謂 ,接著往下傳。所以 USB 攝像頭采用的是 USB 同步傳輸模式。

現(xiàn)在淘寶上的 USB 攝像頭,打開(kāi)它的說(shuō)明書(shū),一般會(huì)支持多種分辨率:從16*16到高清720P多種格式。不同分辨率的視頻傳輸,對(duì)于一幀圖像數(shù)據(jù),對(duì) USB 的傳輸數(shù)據(jù)包的大小和個(gè)數(shù)需求是不一樣的。那USB到底該如何設(shè)計(jì),去適配這種不同大小的數(shù)據(jù)傳輸要求,但又不影響 USB 的其它傳輸模式呢?答案就在結(jié)構(gòu)體內(nèi)的這個(gè)零長(zhǎng)度數(shù)組上。

當(dāng)用戶(hù)設(shè)置不同的分辨率傳輸視頻,USB 就需要使用不同大小和個(gè)數(shù)的數(shù)據(jù)包來(lái)傳輸一幀視頻數(shù)據(jù)。通過(guò)零長(zhǎng)度數(shù)組構(gòu)成的這個(gè)變長(zhǎng)結(jié)構(gòu)體就可以滿(mǎn)足這個(gè)要求??梢愿鶕?jù)一幀圖像數(shù)據(jù)的大小,靈活地去申請(qǐng)內(nèi)存空間,滿(mǎn)足不同大小的數(shù)據(jù)傳輸。但這個(gè)零長(zhǎng)度數(shù)組又不占用結(jié)構(gòu)體的存儲(chǔ)空間,當(dāng) USB 使用其它模式傳輸時(shí),不受任何影響,完全可以當(dāng)這個(gè)零長(zhǎng)度數(shù)組不存在。所以,不得不說(shuō),這樣的設(shè)計(jì)真是妙!

5.4 思考:為什么不使用指針來(lái)代替零長(zhǎng)度數(shù)組?

大家在各種場(chǎng)合,可能常常會(huì)看到這樣的字眼:數(shù)組名在作為函數(shù)參數(shù)進(jìn)行參數(shù)傳遞時(shí),就相當(dāng)于是一個(gè)指針。在這里,我們千萬(wàn)別被這句話(huà)迷惑了:數(shù)組名在作為函數(shù)參數(shù)傳遞時(shí),確實(shí)傳遞的是一個(gè)地址,但數(shù)組名絕不是指針,兩者不是同一個(gè)東西。數(shù)組名用來(lái)表征一塊連續(xù)內(nèi)存存儲(chǔ)空間的地址,而指針是一個(gè)變量,編譯器要給它單獨(dú)再分配一個(gè)內(nèi)存空間,用來(lái)存放它指向的變量的地址。我們看下面這個(gè)程序。

struct buffer1{
    int len;
    int a[0];
};
struct buffer2{
    int len;
    int *a;
};
int main(void)
{
    printf("buffer1: %d\n", sizeof(struct buffer1));
    printf("buffer2: %d\n", sizeof(struct buffer2));
    return 0;
}

運(yùn)行結(jié)果分別為:

buffer1:4
buffer2:8

對(duì)于一個(gè)指針變量,編譯器要為這個(gè)指針變量單獨(dú)分配一個(gè)存儲(chǔ)空間,然后在這個(gè)存儲(chǔ)空間上存放另一個(gè)變量的地址,我們就說(shuō)這個(gè)指針指向這個(gè)變量。而數(shù)組名,編譯器不會(huì)再給其分配一個(gè)存儲(chǔ)空間的,它僅僅是一個(gè)符號(hào),跟函數(shù)名一樣,用來(lái)表示一個(gè)地址。我們接下來(lái)看另一個(gè)程序。

//hello.c
int array1[10] ={1,2,3,4,5,6,7,8,9};
int array2[0];
int *p = &array1[5];
int main(void)
{
    return 0;
}

在這個(gè)程序中,我們分別定義一個(gè)普通數(shù)組、一個(gè)零長(zhǎng)度數(shù)組和一個(gè)指針變量。其中這個(gè)指針變量 p 的值為 array1[5] 這個(gè)數(shù)組元素的地址,也就是說(shuō)指針 p 指向 arraay1[5]。我們接著對(duì)這個(gè)程序使用 arm 交叉編譯器進(jìn)行編譯,并進(jìn)行反匯編。

$ arm-linux-gnueabi-gcc hello.c -o a.out
$ arm-linux-gnueabi-objdump -D a.out

從反匯編生成的匯編代碼中,我們找到 array1 和指針變量 p 的匯編代碼。

00021024 <array1>:
   21024:    00000001    andeq   r0, r0, r1
   21028:    00000002    andeq   r0, r0, r2
   2102c:    00000003    andeq   r0, r0, r3
   21030:    00000004    andeq   r0, r0, r4
   21034:    00000005    andeq   r0, r0, r5
   21038:    00000006    andeq   r0, r0, r6
   2103c:    00000007    andeq   r0, r0, r7
   21040:    00000008    andeq   r0, r0, r8
   21044:    00000009    andeq   r0, r0, r9
   21048:    00000000    andeq   r0, r0, r0
0002104c <p>:
   2104c:    00021038    andeq   r1, r2, r8, lsr r0
Disassembly of section .bss:

00021050 <__bss_start>:
   21050:    00000000    andeq   r0, r0, r0

從匯編代碼中,可以看到,對(duì)于長(zhǎng)度為10的數(shù)組 array1[10],編譯器給它分配了從 0x21024--0x21048 一共40個(gè)字節(jié)的存儲(chǔ)空間,但并沒(méi)有給數(shù)組名 array1 單獨(dú)分配存儲(chǔ)空間,數(shù)組名 array1 僅僅表示這40個(gè)連續(xù)存儲(chǔ)空間的首地址,即數(shù)組元素 array1[0] 的地址。而對(duì)于 array2[0] 這個(gè)零長(zhǎng)度數(shù)組,編譯器并沒(méi)有給它分配存儲(chǔ)空間,此時(shí)的 array2 僅僅是一個(gè)符號(hào),用來(lái)表示內(nèi)存中的某個(gè)地址,我們可以通過(guò)查看可執(zhí)行文件 a.out 的符號(hào)表來(lái)找到這個(gè)地址值。

$ readelf -s  a.out
    88: 00021024    40 OBJECT  GLOBAL DEFAULT   23 array1
    89: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 _bss_end__
    90: 00021050     0 NOTYPE  GLOBAL DEFAULT   23 _edata
    91: 0002104c     4 OBJECT  GLOBAL DEFAULT   23 p
    92: 00010480     0 FUNC    GLOBAL DEFAULT   14 _fini
    93: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 __bss_end__
    94: 0002101c     0 NOTYPE  GLOBAL DEFAULT   23 __data_start_
    96: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    97: 00021020     0 OBJECT  GLOBAL HIDDEN    23 __dso_handle
    98: 00010488     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    99: 0001041c    96 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    100: 00021054     0 OBJECT  GLOBAL DEFAULT   24 array2
    101: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 _end
    102: 000102d8     0 FUNC    GLOBAL DEFAULT   13 _start
    103: 00021054     0 NOTYPE  GLOBAL DEFAULT   24 __end__
    104: 00021050     0 NOTYPE  GLOBAL DEFAULT   24 __bss_start
    105: 00010400    28 FUNC    GLOBAL DEFAULT   13 main
    107: 00021050     0 OBJECT  GLOBAL HIDDEN    23 __TMC_END__
    110: 00010294     0 FUNC    GLOBAL DEFAULT   11 _init

從符號(hào)表里可以看到,array2 的地址為 0x21054,在程序 bss 段的后面。array2 符號(hào)表示的默認(rèn)地址是一片未使用的內(nèi)存空間,僅此而已,編譯器絕不會(huì)單獨(dú)再給其分配一個(gè)內(nèi)存空間來(lái)存儲(chǔ)數(shù)組名??吹竭@里,也許你就明白了:數(shù)組名和指針并不是一回事,數(shù)組名雖然在作為函數(shù)參數(shù)時(shí),可以當(dāng)一個(gè)地址使用,但是兩者不能劃等號(hào)。菜刀有時(shí)候可以當(dāng)武器用,但是你不能說(shuō)菜刀就是武器。

至于為什么不用指針,很簡(jiǎn)單。使用指針的話(huà),指針本身也會(huì)占用存儲(chǔ)空間不說(shuō),根據(jù)上面的 USB 驅(qū)動(dòng)的案例分析,你會(huì)發(fā)現(xiàn),它遠(yuǎn)遠(yuǎn)沒(méi)有零長(zhǎng)度數(shù)組用得巧妙——不會(huì)對(duì)結(jié)構(gòu)體定義造成冗余,而且使用起來(lái)也很方便。

本教程根據(jù) C語(yǔ)言嵌入式Linux高級(jí)編程視頻教程 第05期 改編,電子版書(shū)籍可加入QQ群:475504428 下載,更多嵌入式視頻教程,可關(guān)注:
微信公眾號(hào):宅學(xué)部落(armlinuxfun)
51CTO學(xué)院-王利濤老師:http://edu.51cto.com/sd/d344f

當(dāng)前題目:嵌入式C語(yǔ)言自我修養(yǎng)05:零長(zhǎng)度數(shù)組
URL地址:http://bm7419.com/article4/gejpoe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計(jì)營(yíng)銷(xiāo)型網(wǎng)站建設(shè)、建站公司、ChatGPT、做網(wǎng)站、網(wǎng)站策劃

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶(hù)投稿、用戶(hù)轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話(huà):028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

成都網(wǎng)頁(yè)設(shè)計(jì)公司