1. 程式人生 > >深入理解字串處理函式 strlen() strcpy() strcat() strcmp()

深入理解字串處理函式 strlen() strcpy() strcat() strcmp()

在linux C 程式設計中,我們經常遇到字串的處理,最多的就是字串的長度、拷貝字串、比較字串等;當然現在的C庫中為我們提供了很多字串處理函式。熟練的運用這些函式,可以減少程式設計工作量,這裡介紹幾個常用的字串函式,並編寫一些程式,如果沒有這些庫函式,我們將如何實現其功能;

1.求字串長度函式 strlen
標頭檔案:string.h 
函式原型:size_t strlen(const char *s) 
功能:求字串長度(不含字串結束標誌’\0’) 
如果沒有這個函式,我們如何實現strlen呢? 
程式如下:
 

#include <stdio.h>  
#include <string.h>  

int mystrlen(const char *p)  
{  
    int i = 0;  
    while(p[i])  
        i++;  

    return i;  
}  

int main()  
{  
    int len;  
    char str[] = "Helloworld";  
    len = mystrlen(str);  
    printf("len = %d\n",len);  

    return 0;  
}  

執行結果如下: 
[email protected]:~/qiang/string$ gcc -o strlen strlen.c

[email protected]:~/qiang/string$ ./strlen 
len = 10 
同樣可以實現求字串長度功能。 
既然在講strlen(),在這裡多說明一下,注意strlen()與sizeof()的區別: 
 sizeof是一個操作符,strlen是庫函式。 
 sizeof的引數可以是資料的型別,也可以是變數,而strlen只能以結尾為‘\0‘的字串作引數。 
 編譯器在編譯時就計算出了sizeof的結果。而strlen函式必須在執行時才能計算出來。並且sizeof計算的是資料型別佔記憶體的大小,而strlen計算的是字串實際的長度。 
陣列做sizeof的引數不退化,傳遞給strlen就退化為指標了。 
注意:有些是操作符看起來像是函式,而有些函式名看起來又像操作符,這類容易混淆的名稱一定要加以區分,否則遇到陣列名這類特殊資料型別作引數時就很容易出錯。最容易混淆為函式的操作符就是sizeof。 
說明:指標是一種普通的變數,從訪問上沒有什麼不同於其他變數的特性。其儲存的數值是個整型資料,和整型變數不同的是,這個整型資料指向的是一段記憶體地址。

2.字串拷貝函式strcpy()
標頭檔案:string.h 
函式原型:char *strcpy(char *dest,const char *src) 
功能: 字串拷貝 
引數:src為源串的起始地址,dest為目標串的起始地址 
如果沒有這個函式,我們將如何實現呢?程式如下:
 

#include <stdio.h>  
char *mystrcpy(char *dest,const char *src)  
{  
    char *p;  
    p = dest;  
    while(*src)  
    {  
        *dest++ = *src++;  
    }  
    *dest = '\0';  
    return p;  
}  
int main()  
{  
    const char str1[] = "Helloworld";  
    char str2[30];  
    mystrcpy(str2,str1);  
    printf("str2 = %s\n",str2);  

    return 0;  
}  

執行結果如下: 
[email protected]:~/qiang/string$ ./strcpy

str2 = Helloworld 
同樣能夠得到結果,當然有了strcpy()會很方便;

3.字串連線接函式strcat
標頭檔案:string.h 
函式原型:char *strcat(char *dest,const char *src) 
功能:把字串src連線到字串dest的後面 
實現方法:
 

#include <stdio.h>  
char *mystrcat(char *dest,const char *src)  
{  
    char *p;  
    p = dest;  
    while(*dest)  
        dest++;  
    while(*src)  
    {  
        *dest++ = *src++;  
    }  
    *dest = '\0';  

    return p;  
}  
int main()  
{     
    char str1[] = "hello";  
    char str2[] = "world";  
    mystrcat(str1,str2);  

    printf("str1 = %s\n",str1);  

    return 0;  
}  

執行結果如下: 
[email protected]:~/qiang/string$ gcc -o strcat strcat.c

[email protected]:~/qiang/string$ ./strcat 
str1 = helloworld 
在使用strcat函式時,需要注意,目標陣列應該有足夠的空間,連線源串。注意,目標字串’\0’被刪除,然後連線源串。如果越界會發生什麼呢?我們可以來驗證一下:
 

#include <stdio.h>  
#include <string.h>  
int main()  
{  
    char str2[6] = "world";  
    char str1[6] = "hello";  
    strcat(str1,str2);  
    printf("str1 = %s\n",str1);  

    return 0;  
}  

執行結果如下: 
[email protected]:~/qiang/string$ gcc -o strcat1 strcat1.c

[email protected]:~/qiang/string$ ./strcat1

str1 = helloworld 
* * * stack smashing detected * **: ./strcat1 terminated 
======= Backtrace: ========= 
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0xb76cbd95] 
/lib/i386-linux-gnu/libc.so.6(+0x103d4a)[0xb76cbd4a] 
./strcat1[0x80484d7] 
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb75e14d3] 
./strcat1[0x80483d1] 
======= Memory map: ======== 
08048000-08049000 r-xp 00000000 08:01 830810 /home/fs/qiang/string/strcat1 
當然,下面還有好多,這裡就不展示了,我們來看看結果,helloworld能打印出來,編譯時也沒錯誤,但執行時告知溢位了。 
我們稍微修改程式,大家注意區別:
 

#include <stdio.h>  
#include <string.h>  

int main()  
{  
    char str1[6] = "hello";  
    char str2[6] = "world";  
    strcat(str1,str2);  
    printf("str1 = %s\n",str1);  
    printf("str2 = %s\n",str2);  

    return 0;  
}  

輸出結果如下: 
[email protected]:~/qiang/string$ gcc -o strcat1 strcat1.c

[email protected]:~/qiang/string$ ./strcat1

str1 = helloworld 
str2 = orld 
並沒有溢位!(實際是溢位了!!!!!!!) 
是不是很有意思?大家看看兩者程式的區別,只是六七行交換了位置,但一個溢位,一個卻不溢位,為什麼呢?大家應該知道資料儲存的方式吧,第一個程式中:
 

char str2[6] = "world";  
char str1[6] = "hello";  

先定義了str2[],程式為其分配了一段連續的空間,接著定義了str1[],程式會在剛才為str2[]定義的地址後面接著定義一段連續空間,如果接著將str2 接在str1後面,str1原來只定義了6個位元組,需要連線在一起需要11個位元組,肯定超出了我們定義的地址空間,後面是一片未知區域,會發生溢位;但為什麼程式2卻沒有溢位呢?
 

char str1[6] = "hello";  
char str2[6] = "world";  

這裡就比較巧了,因為str2是在str1後面定義的,str2接在str1後面確實會溢位,但溢位後的一片空間,正好是str2的地址空間,區域是可知的,只是helloworld覆蓋掉了原來str2的東西。所以不會溢位,這樣說,大家明白吧? 
繼續看,大家有木有發現,我在程式二中對str2的值進行了列印,不再是原來的world,變成了orld,按道理來講,str2的值不會改變的啊?大家在這裡要清楚str2只是一個地址而已,只負責輸出當前地址以後的字串,以’\0’結束;所以這裡的orld是strcat(str1,str2)後str1的值,但為什麼是“orld”呢?因為目標字串的’\0’被刪除,然後連線串; 
此時str1後面的orld覆蓋了原world,但str2原來指向的是w的地址,現在原存放’w’的地址處存放的是’o’,所以會輸出”orld”!大家是否還有疑問,後面好像還有個’d’沒有被覆蓋啊,為什麼輸出的不是”orldd”呢?大家應該明白字串有個結束符’\0’吧,它將’d’覆蓋了,如果大家覺得不好理解,可以畫一下圖,就比較清楚了; 
其實這只是個特例,讓大家看一下如果資料溢位造成的後果!

4.字串比較函式strcmp

標頭檔案:string.h 
函式原型:int strcmp(const char *s1,const char *s2) 
功能:按照ASCII碼順序比較字串s1和字串s2的大小 
如果沒有這個函式,我們如下實現:

#include <stdio.h>  

int mystrcmp(const char *s1,const char *s2)  
{  
    int i = 0;  
    while(*s1 || *s2)  
    {  
        if(*s1 > *s2)  
        {  
            return 1;  
        }  
        else if(*s1 < *s2)  
        {  
            return -1;  
        }  
        else  
        {  
            s1++;  
            s2++;  
        }     
    }  
    return 0;  
}  

int main()  
{  
    int n;  
    char str1[] = "hell";  
    char str2[] = "hello";  
    n = mystrcmp(str1,str2);  
    printf("n = %d\n",n);  

    return 0;  
}  

執行結果如下: 
[email protected]:~/qiang/string$ gcc -o strcmp strcmp.c

[email protected]:~/qiang/string$ ./strcmp

n = -1 
剛才提到的函式功能:比較兩字串的大小,好像比較抽象,我們其實是比較兩個字串是否相等;下面我們看個題目: 
題目:計算字串中子串出現的次數 
什麼意思呢?就是helloworldhehehehellowo中,比如說子串”hello”在字串中出現的次數,如果單純的用getchar()獲取每個字元並比較,會很麻煩,在這裡我們可以用strcmp來實現,會很方便,大家可以看看strcmp的具體應用,實現程式如下:
 

#include <stdio.h>  
#include <string.h>  

int main()  
{  
    int i = 0;  
    int count = 0;  
    int len1,len2;  
    char str1[100] = {'\0'};  
    char str2[20] = {'\0'};  
    printf("Please input two strings!\n");  
    scanf("%s%s",str1,str2);  
    len1 = strlen(str1);  
    len2 = strlen(str2);  

    while(i + len2 <= len1)  
    {  
        if(!(strncmp(&str1[i],str2,len2)))  
        {  
            count++;  
            i += len2;  
        }  
        else  
            i++;  
    }  

    printf("count = %d\n",count);  

    return 0;  
}  

執行結果如下: 
[email protected]:~/qiang/string$ ./zichuan

Please input two strings! 
xiaoqiangxiqiangxiaoxiaqiang 
xiao 
count = 2 
[email protected]:~/qiang/string$

大家看看結果是不是正確的。

附:(轉載)
strcmp 字串比較函式,strcpy 字串拷貝函式, strlen 字串測長函式, strcat字串連線函式,sprintf格式化字串拷貝函式等等。因為字串就是以‘\0’結束的一段記憶體,這些函式實質上也就是操作記憶體的函式,所以避免不了的與指標打交道,使得這些函式充滿了陷阱,如果這些函式使用不當,很有可能在程式中埋伏下危險的陷阱,使程式的穩定性遭受重創。下面我就字串使用中一些常見的問題來進行舉例說明。 
一. strcpy:極度危險的函式,一不小心就會中招,危險指數:四星 
strcpy的原型是這樣的: char *strcpy(char *dest, const char *src) 作為常見的字串複製函式,C庫中的實現是不安全的,因為它不做字串的檢查,以至於如果引數傳入了非法指標,比如:src不是指向字串的指標。後果就不堪設想,程式會一直複製,直到遇到‘\0’才結束,這樣很有可能就會使得dest指向的記憶體區域緩衝區溢位,使得導致不程式相干的部分出現錯誤,這種錯誤也許就是致命的。所以使用這個函式一定確保第二個引數傳入合法的指標。 
例子:
 

#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  

char dest[5] = {'D'};  
char mydata[7] = {'m','y','d','a','t','a','\0'};  

int main(void)  
{  
        char i;  
        char source[5];  
        char bound[5] = {'&','&','&','&','&'};  

        for (i = 0; i < 5; i++)  
                source[i] = 'S';  

        printf("before strcopy, mydata is %s\n", mydata);  
        strcpy(dest, source);  
        printf("dest is %s\n", dest);  
        printf("after strcopy, mydata is %s\n", mydata);  

}  

程式中定義了兩個全域性陣列,我們知道C語言的全域性變數要放在DATA段,而dest與mydata因為定義相連,所以其記憶體地址是相鄰的。程式的目的是複製一個字串到dest陣列,而程式中忘了給source陣列最後加上’\0’。所以source就不是一個字串,用它傳遞給strcpy就會造成意想不到的後果。本程式中strcpy一直複製記憶體到dest,直到在遇到‘\0’, 這樣就會多複製很多資料到dest,從而意外的覆蓋mydata,甚至有時還會導致程式崩潰。在拷貝之前, mydata的資料是 “mydata”, 而在拷貝之後造成了意外的修改。 
二. strcat 造成緩衝區溢位的隱形殺手,危險指數 三星 
strcat 是將一個字串連線到另外一個字串上,其函式原型為char *strcat(char *dest,char *src)。這個函式也很危險,因為C語言的實現也是不安全的,傳入非法的指標有可能會造成程式的崩潰。首先保證兩個指標都應該指向字串,其次dest指標指向的空間要足以容的下src指向的字串,否則會造成緩衝區溢位而破壞其他程式資料。 
例子1:
 

#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  
char dest[5] = {'D', '\0'};  
char mydata[7] = {'m','y','d','a','t','a','\0'};  
int main(void)  
{  
        char i;  
        char source[5];  
        char bound[5] = {'&','&','&','&','&'};  

        for (i = 0; i < 4; i++)  
                source[i] = 'S';  
        source[4] = '\0';  
        printf("before strcat, mydata is %s\n", mydata);  
        strcat(dest, source);  
        printf("dest is %s\n", dest);  
        printf("after strcat, mydata is %s\n", mydata);  

}  

這個例子因為目標dest只有5個位元組大小,並且資料佔了一個位元組,只剩下四個位元組位置,而源資料字串長度為4個字元加一個‘\0’有五個位元組大小,所以會多出一個位元組覆蓋了mydata的資料,多出的‘\0’成為了mydata的第一個位元組,導致呼叫strcat後輸出mydata為空。 
例子2 :
 

#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  

char dest[5] = {'D', 'D', 'D', 'D', 'D'};  
char mydata[7] = {'m','y','d','a','t','a','\0'};  
int main(void)  
{  
        char i;  
        char source[5];  
        char bound[5] = {'&','&','&','&','&'};  

        for (i = 0; i < 4; i++)  
                source[i] = 'S';  
        source[4] = '\0';  
        printf("before strcat, mydata is %s\n", mydata);  
        strcat(dest, source);  
        printf("dest is %s\n", dest);  
        printf("after strcat, mydata is %s\n", mydata);  

}  

這個例子中,dest不是字串(沒有‘\0’結尾),導致strcat從地址dest處開始找’\0’,找到’\0’後並在此地址上覆制source的資料,在本程式中就將source連線到了mydata後面,導致mydata變成了“mydataSSSS”,這樣也破壞了程式無關的資料,本程式中還好是破壞的DATA中的資料,如果是其他的資料那麼後果不不僅僅是資料改變這麼簡單了。 
三. strlen 很多malloc函式緩衝區溢位問題的始作俑者 危險指數 二星 
strlen是字串求長函式,但是它求出的長度不包括‘\0’,所以在用malloc分配記憶體的時候,很容易少分配一個位元組,就這小小的一個位元組就會造成緩衝區溢位,我們知道malloc分配的記憶體區域是有一個頭的,這樣就有可能破壞其他malloc的頭使得記憶體釋放失敗,帶來一系列連鎖反映。因為malloc函式的實現與系統有關,這個不好用程式模擬,但是這種情況確實存在。因此如果用strlen求字串長度用於malloc一定要記住要加1。 
四. sprintf 同樣可以造成緩衝區溢位,危險指數 一星 
sprintf是格式化字元拷貝函式,函式原型是int sprintf( char *buffer, const char *format, … ) 。這個函式的實現也是不安全的,使用這個函式要確保buffer足夠大,否則這個函式在不做任何提示的情況下就將buffer溢位,這個函式雖然返回複製的位元組數,可以通過這個檢查複製了多少個位元組,以確定是否緩衝區溢位。但這種亡羊補牢的做法其實沒有實際意義。緩衝區溢位的錯誤已經發生也許會是程式崩潰,檢測的時間也許都沒有,就算有檢測時間,也只是用於提示程式的BUG,在正式的程式中沒有多大用處。 
例子:
 

#include <string.h>  
#include <stdlib.h>  
#include <stdio.h>  

char dest[2] = {'D'};  
char mydata[7] = {'m','y','d','a','t','a','\0'};  

int main(void)  
{  
        char i;  
        char source[5];  
        char bound[5] = {'&','&','&','&','&'};  

        for (i = 0; i < 4; i++)  
                source[i] = 'S';  
        source[4] = '\0';  
        printf("before sprintf, mydata is %s\n", mydata);  
        sprintf(dest, "%s", source);  
        printf("dest is %s\n", dest);  
        printf("after sprintf, mydata is %s\n", mydata);  

} 

這個例子中,目標緩衝區只有兩個位元組的大小,而源字串卻是五個位元組,sprintf在不進行任何提示的情況下,默默的覆蓋了mydata的資料。 
總結 
總上所述,C語言字串操作函式一般都不對引數做檢查,需要呼叫者確保引數的合法性。如果傳入不正確的引數,就會造成緩衝區溢位。輕則資料被修改,重則程式崩潰。最鬱悶的是影響到程式中不相關的部分。我前面舉的例子都很簡單,很容易一眼看出問題的所在,但是大型程式就不會這麼簡單了,這些錯誤就是致命的。所以使用C語言的字串函式時一定要養成良好的習慣,自己檢查引數的合法性,然後再呼叫。目前C語言中這些字串操作函式都有一些安全的版本就是帶n的系列,比如:strncpy,strncat,snprintf。這些函式規定了源字串的大小,對緩衝區溢位的預防有一定的作用,比如:snprintf,其函式原型是int snprintf(char *str, size_t size, const char *format, …) 第二個引數size,可以保證複製size個位元組,如果要複製的字串大於size就會截短,從而保證str不會溢位。程式中儘量使用這些安全的版本。良好的習慣是一個程式穩定與健壯的保證,而良好的習慣都是使用這些常用的函式養成的,所以一定要主要這些字串函式的使用。

原文:https://blog.csdn.net/u013162035/article/details/78612407