你必須知道的指針基礎-4.sizeof計算數組長度與strcpy的安全性問題

1、使用sizeof計算數組長度

1.1 sizeof的基本使用

  若是在做用域內,變量以數組形式聲明,則可使用sizeof求數組大小,下面一段代碼展現瞭如何使用sizeof:c++

    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)。函數

  那麼,爲了不出現沒法計算長度的狀況,咱們通常都會在方法定義時增長一個長度的參數,讓調用者傳遞過來,函數內部再也不計算長度。看看以下的代碼:spa

void printEachWithLen(int* nums,int length)
{
    int i;
    for(i=0;i<length;i++)
    {
        printf("%d ",nums[i]);
    }
}

  這時候,咱們就能夠在main函數中調用該printEachWithLen()函數:3d

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等方法,經過查看其方法定義,都要求傳遞了數組長度。rest

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    

2、strcpy的安全性問題

2.1 使用strcpy複製字符串

  一個簡單的場景,將一個字符串複製到另外一個字符串中,在C語言課本中,最長出現的就是strcpy了。咱們能夠輕易地寫出下面的代碼來實現字符串複製:code

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'結束符,因此是不安全的,複製完後須要手動添加字符串的結束符才行。

  (3)strncpy用法實例:
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語言也能幹大事(第三版)》 

 

相關文章
相關標籤/搜索