練習8:大小和陣列
原文:Exercise 8: Sizes And Arrays
譯者:飛龍
在上一個練習中你做了一些算術運算,不過帶有'\0'
(空)字元。這對於其它語言來說非常奇怪,因為它們把“字串”和“位元組陣列”看做不同的東西。但是C中的字串就是位元組陣列,並且只有不同的列印函式才知道它們的不同。
在我真正解釋其重要性之前,我先要介紹一些概念:sizeof
和陣列。下面是我們將要討論的一段程式碼:
#include <stdio.h>
int main(int argc, char *argv[])
{
int areas[] = {10, 12, 13, 14, 20};
char name[] = "Zed";
char full_name[] = {
'Z', 'e', 'd',
' ', 'A', '.', ' ',
'S', 'h', 'a', 'w', '\0'
};
// WARNING: On some systems you may have to change the
// %ld in this code to a %u since it will use unsigned ints
printf("The size of an int: %ld\n", sizeof(int));
printf("The size of areas (int[]): %ld\n",
sizeof(areas));
printf("The number of ints in areas: %ld\n",
sizeof(areas) / sizeof(int));
printf("The first area is %d, the 2nd %d.\n",
areas[0], areas[1]);
printf("The size of a char: %ld\n", sizeof(char));
printf("The size of name (char[]): %ld\n",
sizeof(name));
printf("The number of chars: %ld\n",
sizeof(name) / sizeof(char));
printf("The size of full_name (char[]): %ld\n",
sizeof(full_name));
printf("The number of chars: %ld\n",
sizeof(full_name) / sizeof(char));
printf("name=\"%s\" and full_name=\"%s\"\n",
name, full_name);
return 0;
}
這段程式碼中我們建立了一些不同資料型別的陣列。由於陣列是C語言工作機制的核心,有大量的方法可以用來建立陣列。我們暫且使用type name[] = {initializer};
語法,之後我們會深入研究。這個語法的意思是,“我想要那個型別的陣列並且初始化為{..}”。C語言看到它時,會做這些事情:
檢視它的型別,以第一個陣列為例,它是
int
。檢視
[]
,看到了沒有提供長度。檢視初始化表示式
{10, 12, 13, 14, 20}
,並且瞭解你想在陣列中存放這5個整數。在電腦中開闢出一塊空間,可以依次存放這5個整數。
將陣列命名為
areas
,也就是你想要的名字,並且在當前位置給元素賦值。
在areas
的例子中,我們建立了一個含有5個整數的陣列來存放那些數字。當它看到char name[] = "Zed";
時,它會執行相同的步驟。我們先假設它建立了一個含有3個字元的陣列,並且把字元賦值給name
。我們建立的最後一個陣列是full_name
,但是我們用了一個比較麻煩的語法,每次用一個字元將其拼寫出來。對C來說,name
和full_name
的方法都可以建立字元陣列。
在檔案的剩餘部分,我們使用了sizeof
關鍵字來問C語言這些東西佔多少個位元組。C語言無非是記憶體塊的大小和地址以及在上面執行的操作。它向你提供了sizeof
便於你理解它們,所以你在使用一個東西之前可以先詢問它佔多少空間。
這是比較麻煩的地方,所以我們先執行它,之後再解釋。
你會看到什麼
$ make ex8
cc -Wall -g ex8.c -o ex8
$ ./ex8
The size of an int: 4
The size of areas (int[]): 20
The number of ints in areas: 5
The first area is 10, the 2nd 12.
The size of a char: 1
The size of name (char[]): 4
The number of chars: 4
The size of full_name (char[]): 12
The number of chars: 12
name="Zed" and full_name="Zed A. Shaw"
$
現在你可以看到這些不同printf
呼叫的輸出,並且瞥見C語言是如何工作的。你的輸出實際上可能會跟我的完全不同,因為你電腦上的整數大小可能會不一樣。下面我會過一遍我的輸出:
譯者注:16位機器上的
int
是16位的,不過現在16位機很少見了吧。
5
我的電腦認為int
的大小是4個位元組。你的電腦上根據位數不同可能會使用不同的大小。
6
areas
中含有5個整數,所以我的電腦自然就需要20個位元組來儲存它。
7
如果我們把areas
的大小與int
的大小相除,我們就會得到元素數量為5。這也符合我們在初始化語句中所寫的東西。
8
接著我們訪問了陣列,讀出areas[0]
和areas[1]
,這也意味著C語言的陣列下標是0開頭的,像Python和Ruby一樣。
9~11
我們對name
陣列執行同樣的操作,但是注意到陣列的大小有些奇怪,它佔4個位元組,但是我們用了三個字元來打出"Zed"。那麼第四個字元是哪兒來的呢?
12~13
我們對full_name
陣列執行了相同的操作,但它是正常的。
13
最後我們列印出name
和full_name
,根據printf
證明它們實際上就是“字串”。
確保你理解了上面這些東西,並且知道這些輸出對應哪些建立的變數。後面我們會在它的基礎上探索更多關於陣列和儲存空間的事情。
如何使它崩潰
使這個程式崩潰非常容易,只需要嘗試下面這些事情:
將
full_name
最後的'\0'
去掉,並重新執行它,在valgrind
下再執行一遍。現在將full_name
的定義從main
函式中移到它的上面,嘗試在Valgrind
下執行它來看看是否能得到一些新的錯誤。有些情況下,你會足夠幸運,不會得到任何錯誤。將
areas[0]
改為areas[10]
並列印,來看看Valgrind
會輸出什麼。嘗試上述操作的不同變式,也對
name
和full_name
執行一遍。
附加題
嘗試使用
areas[0] = 100;
以及相似的操作對areas
的元素賦值。嘗試對
name
和full_name
的元素賦值。嘗試將
areas
的一個元素賦值為name
中的字元。上網搜尋在不同的CPU上整數所佔的不同大小。