嵌入式C語言自我修養 05 零長度陣列

2021-09-10 06:19:33 字數 4679 閱讀 4315

顧名思義,零長度陣列就是長度為0的陣列。

ansi c 標準規定:定義乙個陣列時,陣列的長度必須是乙個常數,即陣列的長度在編譯的時候是確定的。在ansi c 中定義乙個陣列的方法如下:

int  a[10];
c99 新標準規定:可以定義乙個變長陣列。

int len;

int a[len];

也就是說,陣列的長度在編譯時是未確定的,在程式執行的時候才確定,甚至可以由使用者指定大小。比如,我們可以定義乙個陣列,然後在程式執行時才指定這個陣列的大小,還可以通過輸入資料來初始化陣列,示例**如下。

int main(void)

在這個程式中,我們定義乙個零長度陣列,使用 sizeof 檢視其大小可以看到:零長度陣列在記憶體中不占用空間,大小為0。

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

struct buffer;

int main(void)

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

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

struct buffer;

int main(void)

在這個程式中,我們使用 malloc 申請一片記憶體,大小為 sizeof(buffer) + 20,即24個位元組大小。其中4個位元組用來儲存結構體指標 buf 指向的結構體型別變數,另外20個位元組空間,才是我們真正使用的記憶體空間。我們可以通過結構體成員 a,直接訪問這片記憶體。

零長度陣列在核心中,一般以變長結構體的形式使用。今天我們就分析一下 linux 核心中的 usb 驅動。在網絡卡驅動中,大家可能都比較熟悉乙個名字:套接字緩衝區,即 socket buffer,用來傳輸網路資料報。同樣,在 usb 驅動中,也有乙個類似的東西,叫 urb,其全名為 usb request block,即 usb 請求塊,用來傳輸 usb 資料報。

struct urb ;
struct usb_iso_packet_descriptor iso_frame_desc[0];
大家在各種場合,可能常常會看到這樣的字眼:陣列名在作為函式引數進行引數傳遞時,就相當於是乙個指標。在這裡,我們千萬別被這句話迷惑了:陣列名在作為函式引數傳遞時,確實傳遞的是乙個位址,但陣列名絕不是指標,兩者不是同乙個東西。陣列名用來表徵一塊連續記憶體儲存空間的位址,而指標是乙個變數,編譯器要給它單獨再分配乙個記憶體空間,用來存放它指向的變數的位址。我們看下面這個程式。

struct buffer1;

struct buffer2;

int main(void)

執行結果分別為:

buffer1:4

buffer2:8

對於乙個指標變數,編譯器要為這個指標變數單獨分配乙個儲存空間,然後在這個儲存空間上存放另乙個變數的位址,我們就說這個指標指向這個變數。而陣列名,編譯器不會再給其分配乙個儲存空間的,它僅僅是乙個符號,跟函式名一樣,用來表示乙個位址。我們接下來看另乙個程式。

int array1[10] =;

int array2[0];

int *p = &array1[5];

int main(void)

在這個程式中,我們分別定義乙個普通陣列、乙個零長度陣列和乙個指標變數。其中這個指標變數 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 :

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 :

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 驅動的案例分析,你會發現,它遠遠沒有零長度陣列用得巧妙——不會對結構體定義造成冗餘,而且使用起來也很方便。

qq群:475504428

嵌入式C語言自我修養 14 10 道C語言筆試題

1.分析下面的c 它的執行結果可能是 include intmain void 2.關於變數的宣告和定義,下面說法錯誤的是 3.在下面對一些變數的宣告中,有可能是定義語句的是 4.閱讀下面的 可能的執行結果是 include intmain void 5.關於陣列指標的使用,下面哪一條賦值語句可能會...

c語言之零長度陣列

我們設想這樣乙個場景,我們在網路通訊過程中使用的資料緩衝區,緩衝區包括乙個len欄位和data欄位,分別標識資料的長度和傳輸的資料,我們常見的有幾種設計思路 1 定長資料緩衝區,設定乙個足夠大小 max length 的資料緩衝區 2 設定乙個指向實際資料的指標,每次使用時,按照資料的長度動態的開闢...

嵌入式學習(二) 嵌入式系統C 語言

1 從 cpu 復位時的指定位址開始執行 2 跳轉至彙編 startup 處執行 3 跳轉至使用者主程式 main 執行,在 main 中完成 a.初試化各硬體裝置 b.初始化各軟體模組 c.進入死迴圈 無限迴圈 d呼叫各模組的處理函式 下面是幾個 著名 的死迴圈 1 作業系統是死迴圈 2 win3...