中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

嵌入式C語言自我修養 05:零長度數組

發布時間:2020-04-22 19:50:34 來源:網絡 閱讀:297 作者:宅學部落 欄目:系統運維

5.1 什么是零長度數組

顧名思義,零長度數組就是長度為0的數組。

ANSI C 標準規定:定義一個數組時,數組的長度必須是一個常數,即數組的長度在編譯的時候是確定的。在ANSI C 中定義一個數組的方法如下:

int  a[10];

C99 新標準規定:可以定義一個變長數組。

int len;
int a[len];

也就是說,數組的長度在編譯時是未確定的,在程序運行的時候才確定,甚至可以由用戶指定大小。比如,我們可以定義一個數組,然后在程序運行時才指定這個數組的大小,還可以通過輸入數據來初始化數組,示例代碼如下。

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;
}

在這個程序中,我們定義一個變量 len,作為數組的長度。程序運行后,我們可以通過輸入指定數組的長度并初始化,最后再將數組的元素打印出來。程序的運行結果如下:

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 可能覺得變長數組還不過癮,再來一個實錘:支持零長度數組。這下沒有其它編譯器比我狠吧!是的,如果我們在程序中定義一個零長度數組,你會發現除了 GCC 編譯器,在其它編譯環境下可能就編譯通不過或者有警告信息。零長度數組的定義如下:

int a[0];

零長度數組有一個奇特的地方,就是它不占用內存存儲空間。我們使用 sizeof 關鍵字來查看一下零長度數組在內存中所占存儲空間的大小,代碼如下。

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

在這個程序中,我們定義一個零長度數組,使用 sizeof 查看其大小可以看到:零長度數組在內存中不占用空間,大小為0。

零長度數組一般單獨使用的機會很少,它常常作為結構體的一個成員,構成一個變長結構體。

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

零長度數組在結構體中同樣不占用存儲空間,所以 buffer 結構體的大小為4。

5.2 零長度數組使用示例

零長度數組經常以變長結構體的形式,在某些特殊的應用場合,被程序員使用。在一個變長結構體中,零長度數組不占用結構體的存儲空間,但是我們可以通過使用結構體的成員 a 去訪問內存,非常方便。變長結構體的使用示例如下。

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;
}

在這個程序中,我們使用 malloc 申請一片內存,大小為 sizeof(buffer) + 20,即24個字節大小。其中4個字節用來存儲結構體指針 buf 指向的結構體類型變量,另外20個字節空間,才是我們真正使用的內存空間。我們可以通過結構體成員 a,直接訪問這片內存。

通過這種靈活的動態內存申請方式,這個 buffer 結構體表示的一片內存緩沖區,就可以隨時調整,可大可小。這個特性,在一些場合非常有用。比如,現在很多在線視頻網站,都支持多種格式的視頻播放:普清、高清、超清、1080P、藍光甚至4K。如果我們本地程序需要在內存中申請一個 buffer 用來緩存解碼后的視頻數據,那么,不同的播放格式,需要的 buffer 大小是不一樣的。如果我們按照 4K 的標準去申請內存,那么當播放普清視頻時,就用不了這么大的緩沖區,白白浪費內存。而使用變長結構體,我們就可以根據用戶的播放格式設置,靈活地申請不同大小的 buffer,大大節省了內存空間。

5.3 零長度數組在內核中的使用

零長度數組在內核中,一般以變長結構體的形式使用。今天我們就分析一下 Linux 內核中的 USB 驅動。在網卡驅動中,大家可能都比較熟悉一個名字:套接字緩沖區,即 socket buffer,用來傳輸網絡數據包。同樣,在 USB 驅動中,也有一個類似的東西,叫 URB,其全名為 USB request block,即 USB 請求塊,用來傳輸 USB 數據包。

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];
};

在這個結構體內定義了 USB 數據包的傳輸方向、傳輸地址、傳輸大小、傳輸模式等。這些細節我們不深究,我們只看最后一個成員:

struct usb_iso_packet_descriptor iso_frame_desc[0];

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

現在淘寶上的 USB 攝像頭,打開它的說明書,一般會支持多種分辨率:從16*16到高清720P多種格式。不同分辨率的視頻傳輸,對于一幀圖像數據,對 USB 的傳輸數據包的大小和個數需求是不一樣的。那USB到底該如何設計,去適配這種不同大小的數據傳輸要求,但又不影響 USB 的其它傳輸模式呢?答案就在結構體內的這個零長度數組上。

當用戶設置不同的分辨率傳輸視頻,USB 就需要使用不同大小和個數的數據包來傳輸一幀視頻數據。通過零長度數組構成的這個變長結構體就可以滿足這個要求。可以根據一幀圖像數據的大小,靈活地去申請內存空間,滿足不同大小的數據傳輸。但這個零長度數組又不占用結構體的存儲空間,當 USB 使用其它模式傳輸時,不受任何影響,完全可以當這個零長度數組不存在。所以,不得不說,這樣的設計真是妙!

5.4 思考:為什么不使用指針來代替零長度數組?

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

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;
}

運行結果分別為:

buffer1:4
buffer2:8

對于一個指針變量,編譯器要為這個指針變量單獨分配一個存儲空間,然后在這個存儲空間上存放另一個變量的地址,我們就說這個指針指向這個變量。而數組名,編譯器不會再給其分配一個存儲空間的,它僅僅是一個符號,跟函數名一樣,用來表示一個地址。我們接下來看另一個程序。

//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;
}

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

$ 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

從匯編代碼中,可以看到,對于長度為10的數組 array1[10],編譯器給它分配了從 0x21024--0x21048 一共40個字節的存儲空間,但并沒有給數組名 array1 單獨分配存儲空間,數組名 array1 僅僅表示這40個連續存儲空間的首地址,即數組元素 array1[0] 的地址。而對于 array2[0] 這個零長度數組,編譯器并沒有給它分配存儲空間,此時的 array2 僅僅是一個符號,用來表示內存中的某個地址,我們可以通過查看可執行文件 a.out 的符號表來找到這個地址值。

$ 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

從符號表里可以看到,array2 的地址為 0x21054,在程序 bss 段的后面。array2 符號表示的默認地址是一片未使用的內存空間,僅此而已,編譯器絕不會單獨再給其分配一個內存空間來存儲數組名。看到這里,也許你就明白了:數組名和指針并不是一回事,數組名雖然在作為函數參數時,可以當一個地址使用,但是兩者不能劃等號。菜刀有時候可以當武器用,但是你不能說菜刀就是武器。

至于為什么不用指針,很簡單。使用指針的話,指針本身也會占用存儲空間不說,根據上面的 USB 驅動的案例分析,你會發現,它遠遠沒有零長度數組用得巧妙——不會對結構體定義造成冗余,而且使用起來也很方便。

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宣威市| 永平县| 华宁县| 乃东县| 九龙城区| 财经| 临西县| 额敏县| 五指山市| 象州县| 青田县| 临猗县| 库车县| 绥阳县| 东港市| 沙坪坝区| 霸州市| 芦山县| 瓦房店市| 南漳县| 赫章县| 宾川县| 绵竹市| 罗平县| 台东市| 资讯| 靖江市| 民丰县| 藁城市| 阿克| 施秉县| 信丰县| 汝阳县| 胶南市| 东宁县| 邢台县| 独山县| 呼伦贝尔市| 阜城县| 汨罗市| 嘉义市|