一、什么是零長度陣列
零長度陣列就是長度為0的陣列,
ANSI C 標準規定:定義一個陣列時,陣列的長度必須是一個常數,即陣列的長度在編譯的時候是確定的,在ANSI C 中定義一個陣列的方法如下:
型別 陣列名[陣列元素個數];
int array[10];
C99 新標準規定:可以定義一個變長陣列,
int len;
scanf("%d", &len);
int array[len];
也就是說,陣列的長度在編譯時是未確定的,在程式運行的時候才確定,甚至可以由用戶指定大小,比如,我們可以定義一個陣列,然后在程式運行時才指定這個陣列的大小,還可以通過輸入資料來初始化陣列,
程式示例:
#include <stdio.h>
int main(void)
{
int len;
int i = 0;
printf("please input a length: ");
scanf("%d", &len);
int a[len];
for (i = 0; i < len; i++)
{
a[i] = i + 1;
}
for (i = 0; i < len; i++)
{
printf("a[%d] = %d\n", i, a[i]);
}
return 0;
}
執行結果:
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
please input a length: 10
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[5] = 6
a[6] = 7
a[7] = 8
a[8] = 9
a[9] = 10
————————————
在這個程式中,我們定義一個變數 len,作為陣列的長度,程式運行后,我們可以通過輸入指定陣列的長度并初始化,最后再將陣列的元素輸出來,
我們在程式中定義一個零長度陣列,你會發現除了 GCC 編譯器,在其它編譯環境下可能就編譯通不過或者有警告資訊,零長度陣列的定義如下:
#include <stdio.h>
int main(void)
{
//定義長度為零的陣列
int a[0];
return 0;
}
————————————
零長度陣列有一個奇特的地方,就是它不占用記憶體存盤空間,我們使用 sizeof 關鍵字來查看一下零長度陣列在記憶體中所占存盤空間的大小,
程式示例:
#include <stdio.h>
int main(void)
{
int a[0];
printf("sizeof(a): %lu\n", sizeof(a));
return 0;
}
執行結果:
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
sizeof(a): 0
————————————
我們定義一個零長度陣列,使用 sizeof 查看其大小可以看到:零長度陣列在記憶體中不占用空間,大小為0,
零長度陣列一般單獨使用的機會很少,它常常作為結構體的一個成員,構成一個變長結構體 ,
程式示例:
#include <stdio.h>
struct student
{
int id;
char sex;
int a[0];
};
int main(void)
{
int a[0];
printf("sizeof(struct): %lu\n", sizeof(struct student));
return 0;
}
執行結果:
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
sizeof(struct): 8
————————————
零長度陣列在結構體中同樣不占用存盤空間,所以 student結構體的大小為8,
二、零長度陣列應用
零長度陣列經常以變長結構體的形式,在某些特殊的應用場合,被程式員使用,在一個變長結構體中,零長度陣列不占用結構體的存盤空間,但是我們可以通過使用結構體的成員 a 去訪問記憶體,非常方便,
程式示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct student
{
int id;
char sex;
char a[0];
};
int main(void)
{
struct student *s = NULL;
s = malloc(sizeof(struct student) + 20);
if (NULL == s)
{
printf("malloc failed..\n");
return 1;
}
memset(s, 0, sizeof(struct student) + 20);
s->id = 1;
s->sex = 'M';
strcpy(s->a, "hello world");
printf("id: %d sex: %c a: %s\n", s->id, s->sex, s->a);
free(s);
return 0;
}
執行結果:
deng@itcast:~/tmp$ gcc 7.c
deng@itcast:~/tmp$ ./a.out
id: 1 sex: M a: hello world
————————————
在這個程式中,我們使用 malloc 申請一片記憶體,大小為 sizeof(buffer) + 20,即28個位元組大小,其中8個位元組用來存盤結構體指標 student 指向的結構體型別變數,另外20個位元組空間,才是我們真正使用的記憶體空間,我們可以通過結構體成員 a,直接訪問這片記憶體,
通過這種靈活的動態記憶體申請方式,這個 student結構體表示的一片記憶體緩沖區,就可以隨時調整,可大可小,
這個特性,在一些場合非常有用,比如,現在很多在線視頻網站,都支持多種格式的視頻播放:普清、高清、超清、1080P、藍光甚至4K,
如果我們本地程式需要在記憶體中申請一個 buffer 用來快取解碼后的視頻資料,那么,不同的播放格式,需要的 buffer 大小是不一樣的,
如果我們按照 4K 的標準去申請記憶體,那么當播放普清視頻時,就用不了這么大的緩沖區,白白浪費記憶體,
而使用變長結構體,我們就可以根據用戶的播放格式設定,靈活地申請不同大小的 buffer,大大節省了記憶體空間,
三、指標可以代替零長度陣列?
大家在各種場合,可能常常會看到這樣的字眼:陣列名在作為函式引數進行引數傳遞時,就相當于是一個指標,
在這里,我們千萬別被這句話迷惑了:陣列名在作為函式引數傳遞時,確實傳遞的是一個地址,但陣列名絕不是指標,兩者不是同一個東西,
陣列名用來表征一塊連續記憶體存盤空間的地址,而指標是一個變數,編譯器要給它單獨再分配一個記憶體空間,用來存放它指向的變數的地址,
我們看下面這個程式,
程式示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct s1
{
int len;
int a[0];
};
struct s2
{
int len;
int *a;
};
int main(void)
{
printf("sizeof(s1): %lu\n", sizeof(struct s1));
printf("sizeof(s2): %lu\n", sizeof(struct s2));
return 0;
}
執行結果:
deng@itcast:~/tmp$ ./a.out
sizeof(s1): 4
sizeof(s2): 16
————————————
對于一個指標變數,編譯器要為這個指標變數單獨分配一個存盤空間,然后在這個存盤空間上存放另一個變數的地址,我們就說這個指標指向這個變數,而陣列名,編譯器不會再給其分配一個存盤空間的,它僅僅是一個符號,跟函式名一樣,用來表示一個地址,
程式示例:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int b[0];
int *p = &a[5];
int main(void)
{
return 0;
}
在這個程式中,我們分別定義一個普通陣列、一個零長度陣列和一個指標變數,其中這個指標變數 p 的值為 a[5] 這個陣列元素的地址,也就是說指標 p 指向 a[5],我們接著對這個程式使用 arm 交叉編譯器進行編譯,并進行反匯編,
deng@itcast:~/tmp$ arm-linux-gcc 7.c -o a.out
deng@itcast:~/tmp$ arm-linux-objdump -D a.out > a.dis
deng@itcast:~/tmp$
————————————
從反匯編生成的匯編代碼中,我們找到 array1 和指標變數 p 的匯編代碼,
00011024 <a>:
11024: 00000001 andeq r0, r0, r1
11028: 00000002 andeq r0, r0, r2
1102c: 00000003 andeq r0, r0, r3
11030: 00000004 andeq r0, r0, r4
11034: 00000005 andeq r0, r0, r5
11038: 00000006 andeq r0, r0, r6
1103c: 00000007 andeq r0, r0, r7
11040: 00000008 andeq r0, r0, r8
11044: 00000009 andeq r0, r0, r9
11048: 00000000 andeq r0, r0, r0
0001104c <p>:
1104c: 00011038 andeq r1, r1, r8, lsr r0
Disassembly of section .bss:
————————————
從匯編代碼中,可以看到,對于長度為10的陣列 a[10],編譯器給它分配了從 0x11024–0x11048 一共40個位元組的存盤空間,但并沒有給陣列名 a單獨分配存盤空間,陣列名 a僅僅表示這40個連續存盤空間的首地址,即陣列元素 a[0] 的地址,
而對于 a[0] 這個零長度陣列,編譯器并沒有給它分配存盤空間,此時的 a僅僅是一個符號,用來表示記憶體中的某個地址,我們可以通過查看可執行檔案 a.out 的符號表來找到這個地址值,
78: 000082d4 0 FUNC GLOBAL DEFAULT 12 _start
79: 000082bc 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
80: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
81: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
82: 00008408 0 FUNC GLOBAL DEFAULT 13 _fini
83: 0001104c 4 OBJECT GLOBAL DEFAULT 22 p
84: 00008410 4 OBJECT GLOBAL DEFAULT 14 _IO_stdin_used
85: 0001101c 0 NOTYPE GLOBAL DEFAULT 22 __data_start
86: 00011050 0 NOTYPE GLOBAL DEFAULT ABS __bss_start__
87: 0000841c 0 NOTYPE GLOBAL DEFAULT ABS __exidx_end
88: 00011024 40 OBJECT GLOBAL DEFAULT 22 a
89: 00011020 0 OBJECT GLOBAL HIDDEN 22 __dso_handle
90: 00011054 0 NOTYPE GLOBAL DEFAULT ABS __end__
91: 0000839c 104 FUNC GLOBAL DEFAULT 12 __libc_csu_init
92: 00011054 0 NOTYPE GLOBAL DEFAULT ABS __bss_end__
93: 00011050 0 NOTYPE GLOBAL DEFAULT ABS __bss_start
94: 00011054 0 NOTYPE GLOBAL DEFAULT ABS _bss_end__
95: 00011054 0 OBJECT GLOBAL DEFAULT 23 b
96: 00011054 0 NOTYPE GLOBAL DEFAULT ABS _end
97: 00011050 0 NOTYPE GLOBAL DEFAULT ABS _edata
98: 00008414 0 NOTYPE GLOBAL DEFAULT ABS __exidx_start
99: 00008380 28 FUNC GLOBAL DEFAULT 12 main
100: 00008290 0 FUNC GLOBAL DEFAULT 10 _init
————————————
從符號表里可以看到,b 的地址為 0x11054,在程式 bss 段的后面,b符號表示的默認地址是一片未使用的記憶體空間,僅此而已,編譯器絕不會單獨再給其分配一個記憶體空間來存盤陣列名,
看到這里,也許你就明白了:陣列名和指標并不是一回事,陣列名雖然在作為函式引數時,可以當一個地址使用,但是兩者不能劃等號,菜刀有時候可以當武器用,但是你不能說菜刀就是武器,
至于為什么不用指標,很簡單,使用指標的話,指標本身也會占用存盤空間不說,根據上面的 USB 驅動的案例分析,你會發現,它遠遠沒有零長度陣列用得巧妙——不會對結構體定義造成冗余,而且使用起來也很方便,
—— END ——
看到這里你是不是對C語言又有了一點新的認知呢~
如果你喜歡這篇文章的話,動動小指,點個贊再走~
如果你想學編程,小編推薦一個C語言/C++編程學習基地【點擊進入】!

一個活躍、高逼格、高層次的編程學習殿堂;編程入門只是順帶,思維的提高才有價值!
涉及:編程入門、游戲編程、網路編程、Windows編程、Linux編程、Qt界面開發、黑客等等....
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/173097.html
標籤:C
