一、C语言中的空字符串、空指针
str族函数必须保证传入的字符串以 '\0' 结尾,不能是空指针,否则会报错。
1、空指针
char *str1 = nullptr; // 空指针
printf("%d\n", strlen(str1)); // 报错,strlen(nullptr)
2、空字符串
//char str2[1] = { '\0' };
char *str2 = "";
printf("%d\n", strlen(str2)); // 0
二、C语言中的三种 - 字符串拷贝函数
1、strcpy
strcpy是一种C语言的标准库函数,strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回指向dest的指针。
char *strcpy(char* dest, const char *src);
说明:src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串。
char* strcpy(char* dest, const char* src){
assert((dest != NULL) && (src != NULL));
char* p = dest;
while((*p++ = *src++) != '\0');
return dest;
}
2、strlcpy,给定缓冲区大小,截断式拷贝,自动加'\0'
strlcpy,是更加安全版本的strcpy函数,在已知目的地址空间大小为 size 的情况下,把从 src 地址开始且含有 '\0' 结束符的字符串复制到以 dest 开始的地址空间,并不会造成缓冲区溢出,返回实际拷贝的 src 字符串的大小。
size_t strlcpy(char *dest, const char *src, size_t size)
说明:保证尾部加 '\0',采用截断方式,strlcpy
并不属于 ANSI C
,至今也还不是标准。
#include <stdio.h>
#include <string.h>
#include <assert.h>
/*把字符串src拷贝到dest,dest缓冲区大小为size,截断方式拷贝,返回拷贝的实际大小*/
size_t strlcpy(char *dest, const char *src, size_t size){
assert(dest != NULL && src != NULL); //
if(size == 0 || size == 1) return 0; //缓冲区太小,至少为2,可放入至少一个有效字符和一个'\0'
size_t len = strlen(src);
if(len == 0) return 0; //空串
int n = len <= size-1 ? len : size-1; //留下一个放'\0'
for(int i=0; i<n; i++) *dest++ = *src++;
*dest = '\0';
return n;
}
int main()
{
char buf[5];
char src[10] = "12345678";
strlcpy(buf, src, sizeof(buf));
printf("%s\n",buf); // 输出1234
return 0;
}
3、strncpy,指定要拷贝多少个字符,不自动加'\0'
strncpy,从字符串 src 中拷贝指定的 n 个字符 到 字符数组dest 中。
char *strncpy(char *dest, char *src, int n);
说明:复制字符串 src 中的内容到字符串 dest 中,复制多少由 n 的值决定。如果 src 的前 n 个字符不含 NULL 字符,则结果不会以NULL 字符结束。如果 n <= src 的长度,只是将 src 的前 n 个字符复制到 dest 的前 n 个字符,不自动添加 '\0',也就是结果 dest 不包括 '\0'。如果 n > src 的长度,则以 NULL 填充 dest 直到复制完 n 个字节。src 和 dest 所指内存区域不可以重叠且 dest 必须有足够的空间来容纳 src 的字符长度 + '\0'。
/*把字符串中src的前n个字符复制到数组dest中,默认dest是有效地址,其内存大小无从得知,默认src是有效字符串*/
char *strncpy(char *dest, const char *src, int n){
assert(dest != 0 && src != 0);
int len = strlen(src);
char *p = dest;
if(n <= len){ // 拷贝n个有效字符
while(n--){
*p++ = *src++;
}
}
else{ // 拷贝有效字符,不足n则补上'\0'
int r = n - len;
while(len--){
*p++ = *src++;
}
while(r--){
*p++ = '\0';
}
}
return dest;
}
int main()
{
char buf[5];
char src[10] = "12345678";
strlcpy(buf, src, 5); // 12345, 无'\0'
return 0;
}
三、其他拷贝函数
1、strcpy_s,给定缓冲区大小,完整串拷贝,包括原字符串的'\0'
errno_t strcpy_s(char *dest, size_t size, const char *src);
必须保证 size >= strlen(src) + 1,即需要考虑拷贝 '\0'。
//需要可以放下包括'\0'在内的大小
void strcpy_s1(char *dest, size_t size, const char *src) {
size_t len;
assert(dest != NULL && src != NULL);
len = strlen(src) + 1; //len包含'\0'
assert(len <= size);
while (len--) {
*dest++ = *src++;
}
}
2、memcpy,内存拷贝,而非串拷贝,指定要拷贝的字节数
即可用于字符串拷贝(代替strncpy),也可以用于非字符串类型或其数组的拷贝。两者的区别是字符串拷贝要以 '\0' 来判别字符串的长度,而其他类型的或者其数组没有结尾的 '\0'。
void *memcpy1(void *dest, const void * src, size_t size) {
if (dest == NULL || src == NULL) { return dest; }
char *d = (char *)dest;
char *s = (char *)src;
while (size--) {
*d++ = *s++;
}
return dest;
}
int main()
{
int a[4];
int b[4] = { 1,2,3,4 };
memcpy(a, b, sizeof(b)); // 1,2,3,4
return 0;
}
四、用法总结
1、如果 src 是字符串,要拷贝整个字符串,可以使用下列方式。
字符串可以是指针引用方式,也可以是数组保存方式,两者都可以以 strlen(p)+1 来计算整个串长。
// 默认的拷贝整个串
strcpy(dest, src);
// 指定缓冲区大小
strlcpy(dest, src, sizeof(dest)); // 截断方式,放不下就截断,现在在<string.h>中找不到
strcpy_s(dest, sizeof(dest), src); // 完整拷贝,放不下就报错
// 指定要拷贝的字节数
strncpy(dest, src, strlen(src)+1); // 放的超长就补'\0',效率低,现在在<string.h>中找不到
memcpy(dest, src, strlen(src)+1);
2、通常的做法
(1)如果要拷贝整个字符串,strncpy、strlcpy 现在在<string.h>中找不到,strcpy 编译器经常会警告。因此推荐使用的是
strcpy_s(dest, sizeof(dest), src); // 完整拷贝,放不下就报错
memcpy(dest, src, strlen(src)+1); // 完整拷贝字符串
(2)如果要拷贝非字符串类型,如 int、class
memcpy(dest, src, sizeof(src));
3、strcpy 和 strcpy_s 在安全方面的比较
当有缓冲区溢出的问题时, 使用 strcpy_s 函数则会抛出一个异常。而使用 strcpy 函数的结果则未定,因为它错误地改变了程序中其他部分的内存的数据,可能不会抛出异常但导致程序数据错误,也可能由于非法内存访问抛出异常。
使用新的增强安全的CRT函数有什么好处呢?简单地说,新的函数加强了对参数合法性的检查以及缓冲区边界的检查,如果发现错误,会返回 errno 或抛出异常。老版本的这些CRT函数则没有那么严格的检查与校验,如果错误地传输了参数或者缓冲区溢出,那么错误并不能被立刻发现,对于定位程序错误也带来更大困难。
简单的说,如果存在缓冲区溢出,strcpy_s 在编译期就做了检查,可以检查出错误,而 strcpy 在编译时可能检查不出来,在运行期间溢出时才可能被监测到。