前言

字符串在 C 语言中是一种常见的数据类型,以字符数组的形式存储。而 C 语言中的字符串以 \0 (空字符)作为结束符。然而, \0 的存在可能导致一些问题,特别是在处理包含 \0 的字符串时,常见的字符串处理函数可能会出现截断的情况。

最近就在字符串上踩了坑。

坑一

字符串截断问题

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    const char* str = "abcd\0efg";
    printf("str len: %lu, str: %s", strlen(str), str);
    return 0;
}

运行后结果如下

1
str len: 4, str: abcd

可以看到 8 个字符的长度变成了 4,并且字符串被截断了。这并不符合我们的预期。

踩坑二

来看下不以 \0 结尾的问题

1
2
3
4
5
6
7
8
9
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
    char str[4] = "abcd";
    printf("str len: %lu", strlen(str));
    return 0;
}

结果如下

1
2
3
4
error: initializer-string for char array is too long
    char str[4] = "abcd";
                  ^~~~~~
1 error generated.

再运行一次

1
str len: 10, sizeof str: 4

会发现每次运行的结果都不尽相同,因为 strlen 需要碰到 \0 才会结束,所以 strlen 在定义的数组长度之外找到 \0 才算结束,这就导致越界访问了。如果写代码的时候漏写了 \0 ,使用这种方式隐患就很大。

如何处理字符串长度

根据上面的两个例子知道,在 C 语言中字符串就是字符数组,字符串以 \0 结尾,这会导致 \0 后面的字符被截断,如果不以 \0 结尾又会导致越界访问,这种行为很可能会带来隐患。

为了正确处理字符串长度,我们可以采取以下方法:

显式传递字符串的长度:在使用字符串时,我们可以显式地传递字符串的长度,避免依赖 \0 结尾的方式。下面是一个打印字符串的示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>
#include <string.h>

void printString(const char* str, size_t length) {
    fwrite(str, sizeof(char), length, stdout);
}

int main(int argc, char *argv[])
{
    const char* str = "abcd\0efg";
    printString(str, 8);
    return 0;
}

结果如下

1
abcdefg

这里可以打印出包含 \0 的字符串。

C++中的字符串

C++中的字符串一样会有截断的问题,如下所示

1
2
std::string str("abcd\0efg");
std::cout << str.length() << ":" << str << std::endl;

结果如下

1
4:abcd

这时候我们可以通过在构造函数中指定字符串的长度来解决

1
2
std::string str("abcd\0efg", 8);
std::cout << str.length() <<  ":" << str << std::endl;

结果如下

1
8:abcdefg

总结

正确处理字符串长度是编程中一个重要的注意事项,可以避免由于截断导致的错误结果和越界访问。通过采取适当的处理方法,我们可以确保字符串的完整性和正确性。

了解和掌握这些字符串处理技巧将有助于开发人员避免潜在的问题,并编写出更稳健和可靠的代码。

参考

char[]转std::string时可能导致截断_char stdstring_guandafa 的博客-CSDN博客