Chinaunix首页 | 论坛 | 博客
  • 博客访问: 454064
  • 博文数量: 96
  • 博客积分: 6045
  • 博客等级: 大校
  • 技术积分: 906
  • 用 户 组: 普通用户
  • 注册时间: 2007-07-03 11:29
文章分类

全部博文(96)

文章存档

2014年(1)

2012年(22)

2011年(23)

2010年(31)

2008年(1)

2007年(18)

分类: C/C++

2012-01-07 14:02:37

在 C 語言裡,要建立一個字元陣列的字串,常常會使用 這個函數來做格式化的處理。但是實際上,這個函式卻不是那麼「安全」。怎麼說呢?sprintf() 的整個介面長的樣子的是:

int sprintf ( char * str, const char * format, ... )

也就是在使用前,必須要先建立好一個字元陣列的空間,再用這個函式把內容填入,下面就是簡單的例子:

int tmp = 10;char cstr[20];sprintf( cstr, "%d * %d = %d", tmp, tmp, tmp * tmp );

在 這個例子裡,cstr 最後的值,會是「10 * 10 = 100」,看起來好像很好?但是如果把 tmp 的值改成 10000 的話,cstr 則應該要變成「10000 * 10000 = 100000000」,但是由於這時候的字串所需長度為 26,而要寫入的 cstr 的長度只有 20,所以就會造成 buffer overflow 的問題。

像如果是以 Visual C++ 2006 來編譯的話,如果程式裡有用到 sprintf(),他在編譯時就會顯示一個警告訊息:

warning C4996: 'sprintf': This function or variable may be unsafe. Consider using sprintf_s instead.

而要怎麼避免 sprintf 的 buffer overflow 的問題呢?除了微軟建議的 外,實際上在 C99 裡, 也多了一個 是用來取代現有的 sprintf() 了∼他的介面是:

int snprintf(char *str, size_t size, const char * restrict format, ...)

應該可以明顯看得出來,snprinf() 這個函式比 sprintf() 多了一個參數 size;這個參數的用處,就是用來限制最大的寫入資料量,可以用來避免 buffer overflow。以上面的例子來說,本來寫:

int tmp = 10000;char cstr[20];sprintf( cstr, "%d * %d = %d", tmp, tmp, tmp * tmp );

的話,會產生 buffer overflow 的問題。而如果改成用 snprinf() 的話,就是變成:

int tmp = 10000;char cstr[20];snprintf( cstr, sizeof( cstr ), "%d * %d = %d", tmp, tmp, tmp * tmp );

這樣一來,snprinf() 在把資料寫到 cstr 時,最多就只會寫入 20 個字元(cstr 的長度),而不會有 buffer overflow 的問題了∼

不過比較討厭的是,MSVC 似乎並沒有直接給這個函式,而是另外給了一個 …雖然功能和參數都大同小異,但是光函式名稱不一樣,就已經增加了些麻煩,更別說在行為模式上也有些不同了。而 _snprinf()snprintf() 主要的差別在於:

 _snprintf() (MSVC)snprintf()
回傳值
  • len <= size 時,會回傳 len
  • len > size 時,會回傳負值(-1)
  • 正常狀況會回傳 len
  • 如果有錯誤,回傳負值(-1)
字串內容
  • len < size 時,str 會包含結尾的 '' 
  • len >= size 時,str 不會有結尾的 ''
  • str 會包含結尾的 ''
  • len:要輸出的字串應有的長度,不包含結尾的 ''。
  • sizesnprintf / _snprintf 的第二個參數,寫到 str 的最大資料量。

所以如果要跨平台,大致上應該可以:

  1. 如果是使用 MSVC 的話,自行定義 snprintf,讓它變成 _snprintf
    #ifdef _MSC_VER#define snprintf _snprintf#endif
  2. 實際使用,則就可以直接用 snprintfint tmp = 10000;char cstr[20];int size = sizeof( cstr );int c = snprintf( cstr, size, "%d * %d = %d", tmp, tmp, tmp*tmp );if( c > size || c < 0 ) cstr[ size – 1 ] = ‘’;

這樣應該就可以在 g++ 和 MSVC 的環境裡,都可以避免 buffer overflow 和字串結尾沒有 '' 的問題了∼

但是這樣的做法,實際上是產生一個最大長度為 20 的字串,如果超過的話,雖然不會有 buffer overflow 的問題,但是過長的部分還是要切掉。而如果希望可以針對需要,產生一個夠長的字串的話,其實還可以使用 這個函式。他的用法很簡單,基本上和 sprintf() 很像,只是將第一個參數改成一個 char** 而已;下方就是簡單的範例:

char* cstr;int c = asprintf( &cstr, "%d * %d = %d", tmp, tmp, tmp*tmp );

如此一來,他就會自動替 cstr 產生一塊夠大的記憶體空間,來存放字串了∼

不過 asprintf() 這個函式應該不在 C 語言的標準內,而是 GNU 的 extension(可能要加上「#define     _GNU_SOURCE」才能使用),所以在 MSVC 裡並沒有提供 asprintf() 可以使用。不過雖然沒有現成的可以用,但是還是可以透過執行兩次 snprintf() 來做到同樣的功能∼實作的方法,大致上就是:

char* cstr;int c = snprintf( NULL, 0, "%d * %d = %d", tmp, tmp, tmp*tmp );cstr = new char[ c + 1 ];snprintf( cstr, c + 1, "%d * %d = %d", tmp, tmp, tmp*tmp );

做法的主要概念,就是第一次的 _snprintf() 實際上並不真正的將字串寫到某個空間,而是單純用來取得字串所需要的長度,然後再根據需要的長度來產生字元陣列,並將資料寫入。

例子:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5.  
  6. void main(int argc, char *argv[])
  7. {
  8.     char test[3]={0};
  9.     int len=sizeof(test);
  10.     _snprintf(test,len-1,"abcdef");
  11.     printf(test);
  12. }



阅读(5304) | 评论(0) | 转发(0) |
0

上一篇:CMWAP与CMNET

下一篇:今日关键字

给主人留下些什么吧!~~