C 字串函數

來源:互聯網
上載者:User

C語言並不是一種很方便的語言,它的字串就是一例。按照C語言的定義,“字串就是一段記憶體空間,裡麵包含ASCII字元,並且,以”“結尾,總共能存放n-1個字元。”按照這個描述,字串處理確實很麻煩,還很容易出錯。

為了方便使用者,C語言標準庫向使用者提供了一些字串函數,如字串拷貝、構造、清空等函數,在一定程度上方便了使用者的使用。但是,我無意中發現,這些函數還是有些隱患的。

事情很簡單,我注意到我寫的一些程式,老是有記憶體讀寫錯誤,但是,經過仔細檢查我所有的資料Buffer,以及相關的處理函數,又沒有找到什麼錯誤。於是我把懷疑的目光投向我常用的一些字串處理函數上,如strcpy、sprintf等。在經過幾次仔細地跟蹤之後,我發現記憶體錯誤出自於此。於是,我開始研究如何安全地使用字串這個話題。

1.字串拷貝函數

1.1 不安全的strcpy

首先,我寫了這樣一個測試函數:

     void strcpyTest0()
    {
    int i;
    char szBuf[128];
    for(i=0;i<128;i++) szBuf[i]='*';
    szBuf[127]='';     //構造一個全部是*的字串
    char szBuf2[256];
    for(i=0;i<256;i++) szBuf2[i]='#';
    szBuf2[255]='';   //構造一個全部是#的字串
    strcpy(szBuf,szBuf2);
    printf("%sn",szBuf);
    }

很簡單,把一個字串拷貝到另外一個空間,但是,很不幸,源字串比目標地址要長,因此,程式很悲慘地死去了。

1.2 還是不安全的strncpy

通過上例,我發現我需要在拷貝時多輸入一個參數,來標明目的地址有多長,檢查C語言的庫函數說明,有一個strncpy可以達到這個目的,這個函數的原型如下:

char *strncpy( char *strDest, const char *strSource, size_t count );

好了,這下我們的問題解決了,我寫出了如下代碼:

 void strcpyTest1()
{
       int i;
       char szBuf[128];
       for(i=0;i<128;i++) szBuf[i]='*';
       szBuf[127]='';
       char szBuf2[256];
       for(i=0;i<256;i++) szBuf2[i]='#';
       szBuf2[255]='';
       strncpy(szBuf,szBuf2,128);
       printf("%sn",szBuf);
}

一切都顯得很好,但是,當我輸出結果的時候,發現了問題,字串後面有時會跟幾個奇怪的字元,好像沒有用“”結束,於是我把上面的拷貝語句改成“strncpy(szBuf,szBuf2,8);”,只拷貝8個字元,問題出現了,程式輸出如下:

########***********************************************************************************************************************

果然,當請求的目標地址空間比源字串空間要小的時候,strncpy將不再用“”來結束字串。巨大的隱患。

 

1.3 安全地字串拷貝函數

我仔細想了想,我認為我需要如下一個字串拷貝函數:

1、允許用一個整數界定目標地址空間尺寸。

2、當目標地址空間nD小於源字串長度nS時,應該只拷貝nD個位元組。

3、任何情況下,目標地址空間均應該以“”結束,保持一個合法的字串身份。因此,得到的字串最大長度為nD-1.

於是,我寫了這麼一個字串拷貝函數:

 void xg_strncpy1(char *pD, char *pS,int nDestSize)
{
       memcpy(pD,pS,nDestSize);
       *(pD+nDestSize-1)='';
}

很EASY是不,將這個拷貝函數代入上面的例子,只輸出7個“#”, 結果正確。

1.4 記憶體讀錯誤的思考

本來以為可以就此打住了,不過,沒多久,我就發現一個奇怪的現象,這個函數在VC的Debug模式下有錯誤,但是Release模式下卻一切正常。

我奇怪了很久,終於有一天我忍不住了,決定解決這個問題,我把上面的memcpy用自己的一個複製迴圈代替,單步跟蹤,想看看究竟怎麼回事?

原因找到了,我希望拷貝一個256位元組長的字串,但是,拷貝到第33位元組時出錯,檢查程式,發現我的源字串空間只有32 Bytes,原來,我上面的代碼只是防止了記憶體寫出界,但沒有針對讀出界進行檢查,在VC的Debug模式下,記憶體讀出界也是一種非法錯誤,因此被報錯。

知道了原因,解決就很簡單了,我把上面的拷貝函數改成如下形狀:

 void xg_strncpy2(char *pD, char *pS,int nDestSize)
{
       int nLen=strlen(pS)+1;
       if(nLen>nDestSize) nLen=nDestSize;
       memcpy(pD,pS,nLen);
       *(pD+nLen-1)='';
}

一切OK.

2.字串建構函式

2.1 不安全的sprintf

如同上例,我在修改拷貝函數的同時,我也想到了另外一個我常用的字串建構函式sprintf,顯然,這個函數沒有界定目標地址空間的尺寸,也是不安全的,下面的代碼將會造成崩潰:

 void sprintfTest0()
{
       int i;
       char szBuf[128];
       for(i=0;i<128;i++) szBuf[i]='*';
       szBuf[127]='';
       char szBuf2[256];
       for(i=0;i<256;i++) szBuf2[i]='#';
       szBuf2[255]='';
       sprintf(szBuf,szBuf2);
       printf("%sn",szBuf);
}

 

2.2 還是不安全的_snprintf

查閱庫函數手冊,找到這麼一個函數_snprintf,其函數原型如下:

int _snprintf( char *buffer, size_t count, const char *format [, argument] …… );

這個函數允許界定目標地址尺寸,但是,由於研究拷貝函數的經驗,我懷疑它也有strncpy相同的問題,因此,我寫了這麼一段代碼測試:

 void sprintfTest1()
{
       int i;
       char szBuf[128];
       for(i=0;i<128;i++) szBuf[i]='*';
       szBuf[127]='';

       char szBuf2[256];
       for(i=0;i<256;i++) szBuf2[i]='#';
       szBuf2[255]='';

       _snprintf(szBuf,8,szBuf2);
       printf("%sn",szBuf);
}

果然,程式輸出如下:

########***********************************************************************************************************************

同樣的錯誤,沒有用“”結束,我必須另外想方法。

另外,還發現了另外一個不足,就是這個時候,_snprintf函數返回-1,不再返回列印的字元數,那麼,我們如果使用如下代碼將會造成邏輯錯誤,甚至可能崩潰:

 char szBuf[256];
int nCount=0;
while(1)  //這裡表示迴圈構造
{
       nCount+=_snprintf(szBuf+nCount,256-nCount,”... ...”);  //多個字串構造成一個字串
}

注意,代碼利用_snprintf返回的值,來確定下一個起始點,這很常用,但是,當_snprintf返回-1的時候,有可能會寫到*(szBuf-1)的位置上,典型的記憶體寫出界。

2.3 安全地字串建構函式

經過仔細思考,我構造了如下一個函數:

 int xg_printf(char* szBuf,int nDestSize,char *szFormat, ...)
{
       int nListCount=0;
       va_list pArgList;
       va_start (pArgList,szFormat);
       nListCount+=_vsnprintf(szBuf+nListCount,
              nDestSize-nListCount,szFormat,pArgList);
       va_end(pArgList);
       *(szBuf+nDestSize-1)='';
       return strlen(szBuf);
}

注意,這裡我採用了變參函數設計,為的是和sprintf一樣方便,另外,最後一個return也非常重要,因為很多場合,我們需要知道究竟列印了多少字元。將這段函數代入上面的例子後一切正常。

總結:C語言字串庫函數可能是出於提高效能目的,在一旦條件不夠的時候,往往直接返回,忘了採用“”結束字串。這會造成下一次讀取字串時,資料邊界不可控。格式化列印函數,傳回值設計不合理,不永遠是一個正整數,會造成邏輯隱患。因此,建議大家有興趣可以參考一下我提供的兩個函數。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.