前言
C標準庫原始碼可通過下列兩個網站進行檢視:The GNU C Library、Welcome to uClibc-ng! - Embedded C library
以下學習記錄也是以這兩個網站提供的庫函式原始碼進行學習的。
字串相關
strcpy()函式
標頭檔案:#include <string.h>
函式原型:char *strcpy(char *dest, const char *src);
函式描述:將src指向的字串拷貝到dest,包括結束符'\0'。字串不能重疊,並且dest有足夠的空間接收拷貝的字串。
返回值:返回指向dest存放字串的指標。
函式原型:
char *strcpy(char *dest, const char *src)
{
char *dst = dest;
while ((*dst = *src) != '\0') {
src++;
dst++;
}
return dest;
}
可以看出,函式中並不會檢查dest的空間大小,只會拷貝字串直到遇到src字串的結束符'\0',因此dest的空間大小需要使用者保證。
測試用例一:dest空間大於src指向的字串個數。
#include <stdio.h>
#include <string.h>
int main(void)
{
char *str_original = "0123456789";
char buf_dest[12] = {0};
char *ret = NULL;
int i = 0;
for (i = 0; i < sizeof(buf_dest); i++)
buf_dest[i] = 1;
printf("dest:0x%x\n", buf_dest);
ret = strcpy(buf_dest, str_original);
printf(" ret:0x%x\n", ret);
for (i = 0; i < sizeof(buf_dest); i++)
printf("%d ", buf_dest[i]);
printf("\n");
return 0;
}
編譯,執行結果:
$ ./a.out
dest:0xca8e26c0
ret:0xca8e26c0
48 49 50 51 52 53 54 55 56 57 0 1
可以看出,字串拷貝的時候會拷貝字串結束符'\0'。
測試用例二:dest空間小於src指向的字串個數(錯誤用法)。
#include <stdio.h>
#include <string.h>
int main(void)
{
char *str_original = "0123456789";
char buf_dest[5] = {0};
char *ret = NULL;
int i = 0;
for (i = 0; i < sizeof(buf_dest); i++)
buf_dest[i] = 1;
printf("dest:0x%x\n", buf_dest);
ret = strcpy(buf_dest, str_original);
printf(" ret:0x%x\n", ret);
printf("%s\n", buf_dest);
return 0;
}
編譯,執行:
$ ./a.out
dest:0xe31dee10
ret:0xe31dee10
01234567
由於dest沒有以'\0'結尾,因此printf列印的資訊是錯誤的,訪問的內容已經超過了dest的空間。
測試用例三:記憶體重疊
參考部落格:strcpy函式的實現 - Norcy - 部落格園 (cnblogs.com)
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[12] = "hello";
strcpy(str + 1, str); //存在段錯誤
printf("%s\n", str);
return 0;
}
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[12] = "hello";
strcpy(str, str+1);
printf("%s\n", str); //列印輸出ello
return 0;
}
第一種情況:strcpy(str + 1, str),這種情況下dest指向'e',而src第一個字元為'h',當拷貝的時候,'\0'結束符會被覆蓋掉,導致一直拷貝,陷入死迴圈。
第二種情況:strcpy(str, str+1),這種情況下,僅拷貝"ello"。
strncpy()函式
標頭檔案:#include <string.h>
函式原型:char *strncpy(char *dest, const char *src, size_t n);
函式描述:功能和strcpy函式類似,但僅拷貝src的n位元組給dest。另外如果n位元組中沒有結束符'\0',那麼拷貝完後,dest中是不會有結束符的,這個需要注意。如果src的長度小於n位元組,將會在拷貝src字串之後繼續拷貝結束符給dest,直到滿足n位元組。
函式原型:
char *strncpy (char *s1, const char *s2, size_t n)
{
reg_char c;
char *s = s1;
--s1;
if (n >= 4)
{
size_t n4 = n >> 2;
for ( ; ; )
{
c = *s2++;
*++s1 = c;
if (c == '\0')
break;
c = *s2++;
*++s1 = c;
if (c == '\0')
break;
c = *s2++;
*++s1 = c;
if (c == '\0')
break;
c = *s2++;
*++s1 = c;
if (c == '\0')
break;
if (--n4 == 0)
goto last_chars;
}
n = n - (s1 - s) - 1;
if (n == 0)
return s;
goto zero_fill;
}
last_chars:
n &= 3;
if (n == 0)
return s;
do
{
c = *s2++;
*++s1 = c;
if (--n == 0)
return s;
} while (c != '\0');
zero_fill:
do
*++s1 = '\0';
while (--n > 0);
return s;
}
測試用例一:src字串長度大於n,且前n個字元中存在結束符,則只會拷貝到第一個結束符時就結束拷貝。
#include <stdio.h>
#include <string.h>
int main(void)
{
char str_original[] = {'a', '\0', 'b', '\0', 'c', 'd', 'e', '\0'};
char str_dest[8] = {0};
int i = 0;
strncpy(str_dest, str_original, 8);
for (i = 0; i < 12; i++)
printf("%d ", str_dest[i]);
printf("\n");
return 0;
}
編譯,執行:
$ ./a.out
97 0 0 0 0 0 0 0
測試用例二:src字串長度大於n,且前n個字元中不存在結束符,則會拷貝n個字元。處理這種情況,一般需要自己去在dest末尾新增一個結束符。
#include <stdio.h>
#include <string.h>
int main(void)
{
char str_original[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0'};
char str_dest[5] = {0};
int i = 0;
strncpy(str_dest, str_original, 5);
for (i = 0; i < 5; i++)
printf("%d ", str_dest[i]);
printf("\n");
return 0;
}
編譯,執行:
$ ./a.out
97 98 99 100 101
測試用例三:src字串長度小於n,將會在拷貝src字串之後繼續拷貝結束符給dest,直到滿足n位元組。
#include <stdio.h>
#include <string.h>
int main(void)
{
char str_original[] = {'a', 'b', 'c', 'd', 'e', 'f', '\0'};
char str_dest[12] = {0};
int i = 0;
for (i = 0; i < 12; i++)
str_dest[i] = 1;
strncpy(str_dest, str_original, 12);
for (i = 0; i < 12; i++)
printf("%d ", str_dest[i]);
printf("\n");
return 0;
}
編譯,執行:
$ ./a.out
97 98 99 100 101 102 0 0 0 0 0 0