sizeof、strlen、字串、陣列,整到一塊,你還清楚嗎?
寫在前面
sizeof、strlen、字串、陣列,提到這些概念,相信學過C語言的人都能耳熟能詳,也能談得頭頭是道,但是,在實際運用中,當這些內容交織在一起時,大家卻不一定能搞地清清楚楚,本文的目的正是幫助大家將相關知識總結清楚。
正文
先看一段程式碼
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void testchar(char str[]) 5 { 6 printf("%d %d\n", sizeof(str), strlen(str)); 7 } 8 9 void testint(intarr[]) 10 { 11 printf("%d\n", sizeof(arr)); 12 } 13 14 int main() 15 { 16 char str[] = "abc"; 17 printf("%d %d\n", sizeof(str), strlen(str)); //4 3 18 19 char str1[10] = "abc"; 20 printf("%d %d\n", sizeof(str1), strlen(str1)); //10 3 21 22 char dog[] = "wangwang\0miao"; 23 printf("%d %d\n", sizeof(dog), strlen(dog)); //14 8 24 testchar(dog); //4 8 25 26 char *cat = "wangwang\0miaomiao"; 27 printf("%d %d\n", sizeof(cat), strlen(cat)); //4 8 28 29 int arr[10] = { 0 }; 30 printf("%d %d\n", sizeof(arr), sizeof(arr[11])); //40 4 31 testint(arr); //4 32 33 return0; 34 }
結果
在解釋上面的例子之前,我們先來說一說sizeof和strlen。
語法上的本質不同:
sizeof是運算子,strlen是函式。
適用範圍不一樣:
對sizeof(name)而言,name可以是變數名也可以是型別名,對strlen而言,引數必須是char*型別的,即strlen僅用於字串。
重中之重——從底層看本質
strlen(ptr)的執行機理是:從引數ptr所指向的記憶體開始向下計數,直到記憶體中的內容是全0(即’\0’)為止(不會對’\0’進行計數)。用strlen測量字串的長度,其實就是基於這個原理。
sizeof(name)的執行機理是:如果name是一個型別名,得到的是該型別的大小(所謂型別的大小,指的是:如果存在一個該型別的變數,這個變數在記憶體中所佔用的位元組數),如果name是一個變數名,那麼,sizeof(name)並不會真正訪問該變數,而是先獲知該變數的型別,然後再返回該型別的大小(即便是struct這樣的複雜型別,編譯器在編譯時也會根據它的各個域記錄其大小,所以,由型別得到型別大小,不是一件難事)。換句話說,本質上,sizeof的運算物件是型別。如果name是一個變數名,那麼,sizeof如何“看待”name的型別,將是一個關鍵問題。(後面我們會對這一點有深刻的體會)
上面提到的這一點,是理解好sizeof和strlen的不二法門,是放之四海皆準的準則。下面,我們就以這樣的準則來分析上面的例子。
a.
char str[] = "abc"; printf("%d %d\n", sizeof(str), strlen(str)); //4 3
這裡,是用陣列的形式宣告字串,編譯器會自動在字串後面加上'\0',所以,陣列的元素個數是4而不是3。對於sizeof(str)而言,sizeof將str視為char [4]l型別的變數,所以,sizeof(str)的結果就是整個陣列所佔有的空間大小。對於strlen(str)來說,它從str指向的記憶體開始計數,直到遇到全0的記憶體('\0'),所以最後得到結果3。
b.
char str1[10] = "abc"; printf("%d %d\n", sizeof(str1), strlen(str1)); //10 3
編譯器為char str1[10]分配10個數組元素大小的空間,這與初始化它的字串沒有關係,所以sizeof(str1)得到10。
c.
char dog[] = "wangwang\0miao"; printf("%d %d\n", sizeof(dog), strlen(dog)); //14 8 testchar(dog); //4 8
前兩句和a中的情況相同,sizeof(dog)輸出整個陣列所佔的記憶體大小(包括編譯器加上去的'\0'),strlen(dog)遇到'\0'就停止,所以輸出8。
再看後面的函式呼叫,陣列名dog作為函式實參傳入,我們再來回顧一下testchar函式
void testchar(char str[]) { printf("%d %d\n", sizeof(str), strlen(str)); }
我們發現,這裡sizeof(str)並沒有像sizeof(dog)那樣得到14,而是得到了4。這是因為,str是函式形參,儘管它是以陣列名的形式出現的,傳給它的實參也確實是陣列名,但sizeof僅僅把它當成一個char*型別的指標看待,所以,sizeof(str)的結果就是char *型別所佔的空間4。至於strlen(str),我們前面說過,它執行的機理就是從str指向的記憶體開始向下計數,直到遇到'\0',所以依然得到8。
d.
char *cat = "wangwang\0miaomiao"; printf("%d %d\n", sizeof(cat), strlen(cat)); //4 8
由於cat明確宣告為char*,所以sizeof將它視為指標,得到4。
e.
int arr[10] = { 0 }; printf("%d %d\n", sizeof(arr), sizeof(arr[11])); //40 4 testint(arr); //4
前面說過,當陣列名作為函式形參出現時,sizeof僅僅將其視為一個指標,否則,sizeof認為它代表整個陣列,所以,sizeof(arr)得到整個陣列所佔的位元組數40,而testint(arr)的結果是int*型別的指標的長度4。
sizeof(int[11])中,很明顯陣列越界了,但並不會出現執行時錯誤。原因是:依據我們給出的判斷準則,sizeof並沒有真正訪問arr[11],根據arr的宣告,sizeof知道arr[11]是int型的,所以返回int型別的大小。
至於testint(arr),道理和c中的testchar(dog)相同。
最後,基於上面的討論,給出編碼準則:
1.永遠不要用sizeof來求字串長度!它不是幹這個活的,所以你也永遠不會得到正確答案。
2.不要自作聰明地用sizeof(arr)/sizeof(arr[0])這樣的程式碼求陣列的長度!sizeof也不是幹這個活的。如果arr是函式形參,得到的結果將是錯誤的(除非你在32位系統下恰好宣告int arr[1]或者char arr[4]等,但這純屬巧合)。既然是陣列,長度自然是已知的,求陣列長度這一本身,就是多此一舉的愚蠢行為。
寫在後面
本文的目的,就是使讀者對C語言的基礎知識——sizeof和strlen有一個本質的認識,同時對與之相關的易錯、易混問題有一個正確、清晰的判斷。由於在下才疏學淺,錯誤疏漏之處在所難免,希望廣大讀者積極批評指正,您的批評指正是在下前進的不竭動力。
寫在前面
sizeof、strlen、字串、陣列,提到這些概念,相信學過C語言的人都能耳熟能詳,也能談得頭頭是道,但是,在實際運用中,當這些內容交織在一起時,大家卻不一定能搞地清清楚楚,本文的目的正是幫助大家將相關知識總結清楚。
正文
先看一段程式碼
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void testchar(char str[]) 5 { 6 printf("%d %d\n", sizeof(str), strlen(str)); 7 } 8 9 void testint(int arr[]) 10 { 11 printf("%d\n", sizeof(arr)); 12 } 13 14 int main() 15 { 16 char str[] = "abc"; 17 printf("%d %d\n", sizeof(str), strlen(str)); //4 3 18 19 char str1[10] = "abc"; 20 printf("%d %d\n", sizeof(str1), strlen(str1)); //10 3 21 22 char dog[] = "wangwang\0miao"; 23 printf("%d %d\n", sizeof(dog), strlen(dog)); //14 8 24 testchar(dog); //4 8 25 26 char *cat = "wangwang\0miaomiao"; 27 printf("%d %d\n", sizeof(cat), strlen(cat)); //4 8 28 29 int arr[10] = { 0 }; 30 printf("%d %d\n", sizeof(arr), sizeof(arr[11])); //40 4 31 testint(arr); //4 32 33 return 0; 34 }
結果
在解釋上面的例子之前,我們先來說一說sizeof和strlen。
語法上的本質不同:
sizeof是運算子,strlen是函式。
適用範圍不一樣:
對sizeof(name)而言,name可以是變數名也可以是型別名,對strlen而言,引數必須是char*型別的,即strlen僅用於字串。
重中之重——從底層看本質
strlen(ptr)的執行機理是:從引數ptr所指向的記憶體開始向下計數,直到記憶體中的內容是全0(即’\0’)為止(不會對’\0’進行計數)。用strlen測量字串的長度,其實就是基於這個原理。
sizeof(name)的執行機理是:如果name是一個型別名,得到的是該型別的大小(所謂型別的大小,指的是:如果存在一個該型別的變數,這個變數在記憶體中所佔用的位元組數),如果name是一個變數名,那麼,sizeof(name)並不會真正訪問該變數,而是先獲知該變數的型別,然後再返回該型別的大小(即便是struct這樣的複雜型別,編譯器在編譯時也會根據它的各個域記錄其大小,所以,由型別得到型別大小,不是一件難事)。換句話說,本質上,sizeof的運算物件是型別。如果name是一個變數名,那麼,sizeof如何“看待”name的型別,將是一個關鍵問題。(後面我們會對這一點有深刻的體會)
上面提到的這一點,是理解好sizeof和strlen的不二法門,是放之四海皆準的準則。下面,我們就以這樣的準則來分析上面的例子。
a.
char str[] = "abc"; printf("%d %d\n", sizeof(str), strlen(str)); //4 3
這裡,是用陣列的形式宣告字串,編譯器會自動在字串後面加上'\0',所以,陣列的元素個數是4而不是3。對於sizeof(str)而言,sizeof將str視為char [4]l型別的變數,所以,sizeof(str)的結果就是整個陣列所佔有的空間大小。對於strlen(str)來說,它從str指向的記憶體開始計數,直到遇到全0的記憶體('\0'),所以最後得到結果3。
b.
char str1[10] = "abc"; printf("%d %d\n", sizeof(str1), strlen(str1)); //10 3
編譯器為char str1[10]分配10個數組元素大小的空間,這與初始化它的字串沒有關係,所以sizeof(str1)得到10。
c.
char dog[] = "wangwang\0miao"; printf("%d %d\n", sizeof(dog), strlen(dog)); //14 8 testchar(dog); //4 8
前兩句和a中的情況相同,sizeof(dog)輸出整個陣列所佔的記憶體大小(包括編譯器加上去的'\0'),strlen(dog)遇到'\0'就停止,所以輸出8。
再看後面的函式呼叫,陣列名dog作為函式實參傳入,我們再來回顧一下testchar函式
void testchar(char str[]) { printf("%d %d\n", sizeof(str), strlen(str)); }
我們發現,這裡sizeof(str)並沒有像sizeof(dog)那樣得到14,而是得到了4。這是因為,str是函式形參,儘管它是以陣列名的形式出現的,傳給它的實參也確實是陣列名,但sizeof僅僅把它當成一個char*型別的指標看待,所以,sizeof(str)的結果就是char *型別所佔的空間4。至於strlen(str),我們前面說過,它執行的機理就是從str指向的記憶體開始向下計數,直到遇到'\0',所以依然得到8。
d.
char *cat = "wangwang\0miaomiao"; printf("%d %d\n", sizeof(cat), strlen(cat)); //4 8
由於cat明確宣告為char*,所以sizeof將它視為指標,得到4。
e.
int arr[10] = { 0 }; printf("%d %d\n", sizeof(arr), sizeof(arr[11])); //40 4 testint(arr); //4
前面說過,當陣列名作為函式形參出現時,sizeof僅僅將其視為一個指標,否則,sizeof認為它代表整個陣列,所以,sizeof(arr)得到整個陣列所佔的位元組數40,而testint(arr)的結果是int*型別的指標的長度4。
sizeof(int[11])中,很明顯陣列越界了,但並不會出現執行時錯誤。原因是:依據我們給出的判斷準則,sizeof並沒有真正訪問arr[11],根據arr的宣告,sizeof知道arr[11]是int型的,所以返回int型別的大小。
至於testint(arr),道理和c中的testchar(dog)相同。
最後,基於上面的討論,給出編碼準則:
1.永遠不要用sizeof來求字串長度!它不是幹這個活的,所以你也永遠不會得到正確答案。
2.不要自作聰明地用sizeof(arr)/sizeof(arr[0])這樣的程式碼求陣列的長度!sizeof也不是幹這個活的。如果arr是函式形參,得到的結果將是錯誤的(除非你在32位系統下恰好宣告int arr[1]或者char arr[4]等,但這純屬巧合)。既然是陣列,長度自然是已知的,求陣列長度這一本身,就是多此一舉的愚蠢行為。
寫在後面
本文的目的,就是使讀者對C語言的基礎知識——sizeof和strlen有一個本質的認識,同時對與之相關的易錯、易混問題有一個正確、清晰的判斷。由於在下才疏學淺,錯誤疏漏之處在所難免,希望廣大讀者積極批評指正,您的批評指正是在下前進的不竭動力。