一、使用sizeof計算數組長度
1.1 sizeof的基本使用
如果在作用域內,變量以數組形式聲明,則可以使用sizeof求數組大小,下面一段代碼展示了如何使用sizeof:
int nums[] = {11,22,33,44,55,66}; int i; // sizeof(nums) 計算nums數組的總字節數 // sizeof(int) 計算int類型所占用的字節數 int length = sizeof(nums)/sizeof(int); for(i=0;i<length;i++) { printf("%d ",nums[i]); }
其中sizeof(nums)代表計算nums數組的總字節數,而sizeof(int)則代表計算int類型所占用的字節數(32位系統下是4個字節,64位下可能不同,因此這里使用sizeof(int)可以向程序員屏蔽這個差異),運行結果為:

1.2 sizeof只能在編譯時計算
假如我們將上面的代碼做一個抽象,將數組的遍歷及打印封裝為一個方法,代碼如下:
void printEach(int* nums) { // sizeof(nums)在這里是計算指針的字節數 int length = sizeof(nums)/sizeof(int); printf("The length of nums is %d\n",length); int i; for(i=0;i<length;i++) { printf("%d ",nums[i]); } }
我們定義了一個printEach方法,其參數是一個指針,在方法內部通過sizeof計算數組長度。但是,運行結果並沒有同上面的結果一致:

我們發現,雖然我們使用了指針,但由於sizeof是編譯器在編譯的時候計算的,無法動態計算。因此對於int *或者將數組傳遞給函數,那么就無法使用sizeof獲取大小了。即使函數聲明中寫着int[]也不行(為了避免誤解,不要在參數中聲明數組類型)。這里,sizeof(nums)只是計算了指針的字節數(這里指針指向了數組的首元素的地址,一個int占4個字節,所以最后length變成了1)。
那么,為了避免出現無法計算長度的情況,我們一般都會在方法定義時增加一個長度的參數,讓調用者傳遞過來,函數內部不再計算長度。看看如下的代碼:
void printEachWithLen(int* nums,int length) { int i; for(i=0;i<length;i++) { printf("%d ",nums[i]); } }
這時候,我們就可以在main函數中調用該printEachWithLen()函數:
int length = sizeof(nums)/sizeof(int); printEachWithLen(nums,length);
這下看看結果:

因此,一般給函數傳遞數組/字符串的時候都要求額外傳遞“長度”參數,因為函數內部也不知道“有多長”。例如:memcpy(void * restrict, const void * restrict, size_t),第三個參數size_t就是長度。又例如在.NET中,要進行數組的復制,可以使用 Array.Copy 、Buffer.BlockCopy 、Array.ConstrainedCopy等方法,通過查看其方法定義,都要求傳遞了數組長度。
const int INT_SIZE = 4; int[] arr = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 }; Buffer.BlockCopy(arr, 3 * INT_SIZE, arr, 0 * INT_SIZE, 4 * INT_SIZE); foreach (int value in arr) Console.Write("{0} ", value); // The example displays the following output: // 8 10 12 14 10 12 14 16 18 20
二、strcpy的安全性問題
2.1 使用strcpy復制字符串
一個簡單的場景,將一個字符串復制到另一個字符串中,在C語言課本中,最長出現的就是strcpy了。我們可以輕易地寫出下面的代碼來實現字符串復制:
char sourceStr[] = "hello edison"; char destStr[30]; strcpy(destStr,sourceStr); printf("%s",destStr);
運行結果如下圖所示:

但是,我們常常聽人說strcpy是不安全的函數,為什么呢?先看看strcpy內部的循環判斷條件:
while ((*strDest++ = *strSrc++) != '\0')
這個循環會一直執行,直到循環條件為空,即'\0',也就是說,如果strDest所指的存儲空間不夠大的話,這個函數會將strSrc中的部分內容拷貝到strDest所指內存空間后面的內存中。而strDest所指空間后面的內存卻是不可知的,有可能已經被其他資源占用了,這樣就會破壞原先存儲的內容,導致系統崩潰。
因為strcpy在執行字符串拷貝的時候,會從strSrc所指位置開始,檢測當前內存單元中存儲的數據是否為'\0'。如果不為'\0',則將這個內存單元中的數據拷貝到strDest所指向的內存中。如果strSrc中存儲的字符串長度大於dst所申請的內存空間的話,就會產生越界,造成不可預知的后果。
PS:strlen根據'\0'判斷字符串結束,那么惡意攻擊者可以構造一個不包含'\0'的字符串,然后讓數據寫入數組之外的程序內存空間,從而進行破壞。
2.2 使用strncpy代替strcpy
(1)strncpy函數定義:
char *strncpy(char *dest, const char *src,int count)
將字符串src中的count個字符拷貝到字符串dest中去,最后返回指向dest的指針。
(2)strncpy用法解析:
這個函數和strcpy類似,當src的長度大於dst申請的空間的時候,情況和strcpy一樣;
如果第3個參數count的值大於src中字符串的長度的話,就會將字符串src拷貝到dst中,返回函數。
注意:如果源串長度大於n,則strncpy不復制最后的'\0'結束符,所以是不安全的,復制完后需要手動添加字符串的結束符才行。
char sourceStr[] = "hello edison"; char destStr[30]; int len = sizeof(sourceStr)/sizeof(char); printf("%d\n",len); strncpy(destStr,sourceStr,len-1); // 保證安全的字符串復制 destStr[len-1]='\0'; printf("%s",destStr);
運行結果如下圖所示:

參考資料
如鵬網,《C語言也能干大事(第三版)》
