【C語言】字串函式探幽
目錄
最近學習C++時遇到了字串的問題,結合之前的面試題深入分析下。
字串庫函式有strcpy、strlen、strstr等,可以參見C語言字串操作總結大全。
1、strcpy()
strcpy():char *strcpy(char* dest, const char *src);返回指向dset的指標
strcpy()被認為是不安全的函式,因為它的函式實現非常簡單,沒有考慮異常情況。下面考慮兩種情況:
a)如果src長度大於dest會發生什麼?
linux man page裡面提到了這個BUG:
If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there's enough space. This may be unnecessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.
大概意思是這樣做固定長度的字串就會發生溢位。如果程式沒有事先檢查記憶體空間,就會發生難以預料的事情。
寫好程式碼:char a[2] = "a"; char b[10] = "bbbbbbbb"; auto p=strcpy(a, b); 在VS斷點除錯,步驟如下:
i.執行到strcpy函式之前,檢視a和b的值和記憶體:
發現VS編譯器以16進位制數c來作為未被使用的記憶體,a後面還有6位元組的空記憶體。
ii、執行strcpy,觀察記憶體
發現a雖然只有2個位元組的記憶體,但是後面的記憶體都被覆蓋掉了(對比上下兩圖可以發現值為9f的這個記憶體被覆蓋成00了,其他的CC被62填充)
iii、得出結論
會覆蓋字串陣列之外的記憶體(陣列越界)。假設陣列b無限大,那豈不是把整個堆都覆蓋掉了?果然這個函式非常危險,要改用strncpy()。
b)如果src長度小於dset呢?
src從頭到\0都被複制到dset中,dest多餘的部分沒有變化(右圖)。這種情況沒什麼問題。
c)手寫strcpy
發現網上大家的程式碼都沒能解決陣列越界的問題,我加了一行程式碼 if (*dst == '\0') break; 目前看來沒什麼問題。
char* my_strcpy(char *dst, const char *src) {
assert(dst != NULL && src != NULL);//斷言輸入不為空,空則呼叫abort()異常終止程序
char *ret = dst;
while ((*dst = *src) != '\0') {
dst++;
src++;
if (*dst == '\0') break;//如果此時目標陣列已經結束,就不用繼續複製了
}
return ret;
}
除錯發現執行結束時不會覆蓋dst後面的記憶體了。
d)總結
在一本面試書上看到過,說這個函式是MS考慮到普適性,故意寫的很簡單。不過這個問題也很老了,只是憑興趣研究下。總結起來就是程式設計師要注意記憶體溢位的問題,特別在使用指標的時候,這種小問題很可能會導致一些重大安全問題。
2、strlen()
size_t __cdecl strlen(const char * s){
int i = 0;
while( *s ) {
i++;
s++;
}
return i;
}
3、strcat()
char * __cdecl strcat(char * dst, const char * src){
char *p = dst;
while( *p )
p++;
while( *p ++ = *src ++ )
;
return dst;
}
4、strcmp()
int strcmp(const char *str1, const char *str2){
int ret=0;
while( !(ret = *(unsigned char*)str1 - *(unsigned char*)str2 ) && *str1 )
{
str1++;
str2++;
}
if(ret < 0)
return -1;
else if(ret > 0)
return 1;
return 0;
}