【问题标题】:C strings behave so weirdC 字符串的行为很奇怪
【发布时间】:2019-02-06 15:25:50
【问题描述】:

第一个问题是关于字符串末尾的空字符\0,在何时需要/自动添加\0 方面有很多变化。 声明 char 数组时,是否需要指定\0?或者在什么情况下,我应该指定\0,什么情况下不?谁能给个全面的总结? (如this post)。如果您觉得我的问题模棱两可,那么更具体的问题是在 C 中声明一个字符串时,最好的方法是什么,是char string[] = "first string",因为例如,这样,我可以不用担心strcat(string, another_string)尺寸问题?

第二个问题:我有

1   char a[] = "kenny";
2   char b[3];
3   strncpy(b, a, (int)(sizeof(b) - 1));
4   printf("%i\n", (int)sizeof(b)); // 3
5   printf("string length: %i\n", (int)strlen(b)); // string length: 8
6   printf("%s\n", b); // give me random stuff like kekenny or keEkenny 
  • 3:我只想传2个字节给b
  • 4:sizeof 行为正常
  • 5:可是为什么变成了8???
  • 6:为什么它会给我随机的东西,比如 kekenny 或 keEkenny

我刚刚迷失了 C 字符串中发生的事情。我以前经常使用 C++,但仍然无法理解 C 字符串的行为。

【问题讨论】:

  • 不,你不能“不关心大小问题就做strcat(string, another_string)”。没有串联空间。
  • 为了printf 可以打印字符串,它应该是一个正确的以空字符结尾的字符串。但是您的代码故意截断空终止符。
  • 您的代码将显示 undefined behavior。它可能有时会起作用,然后突然就不起作用了。
  • @WeatherVane 这就是为什么我这样做了strncpy(b, a, (int)(sizeof(b) - 1));,因为我担心尺寸问题,但它仍然在以后的代码中给了我随机的东西?
  • 您叙述中的示例是char string[] = "first string"; strcat(string, another_string); 没有空间,因为string 的大小正好是13 个字节,以'\0' 终止符结尾。

标签: c arrays string


【解决方案1】:

关于 C 字符串的问题在于它们非常低级,您必须记住许多额外的事情,有时需要“手动”完成。

(相比之下,C++ std::strings 只是完全正常的高级类型。)

回答您的具体问题:

您几乎不需要明确地提供\0。几乎唯一一次是在完全手工制作琴弦时。例如,此代码有效:

char str[10];
str[0] = 'c';
str[1] = 'a';
str[2] = 't';
str[3] = '\0';
printf("%s\n", str);

但是,如果您忽略对str[3] 的显式分配,它的行为就会不正常。 (但如果你不这样手动创建字符串,你就不需要这么担心了。)

使用strcpy 复制字符串时必须非常小心。您必须确保目标字符串(“缓冲区”)足够大。 C 中的任何东西都不会为您解决这个问题——没有任何东西可以确保目的地足够大;如果它不够大,没有任何警告你。但如果它不够大,可能会发生最奇怪的事情——包括它似乎有效,尽管它不应该。 (正式名称是“未定义的行为”。)

特别是如果你写

char string[] = "first string";
strcat(string, another_string);

你得到的是一个错误,纯粹而简单。 不是“这样你就不用担心尺寸问题了”。当您说char string[] = "..." 时,编译器将字符串的大小调整到足以容纳初始化程序(及其\0)的大小,在这种情况下,"first string" 为 13 个字节。 [] 确实不是的意思是“使这个字符串足够大,以容纳我将尝试插入其中的任何文本”。

使用strncpy 时必须更加小心。事实上,我的建议是根本不要使用strncpy。它的实际作用是不寻常的、特殊的、难以解释的,而且通常不是你想要的。 (一方面,如果你复制的字符串少于一个完整的字符串,它不会在目的地添加一个 `\0',这有助于解释为什么你会得到像“kekenny”这样的东西。)

【讨论】:

  • 谢谢!!所以当我做char str[10] = {'a', 'b'. 'c'}; 时,我需要明确手动添加\0 到最后吗?
  • 如果你这样做,是的。另请参阅this old question
  • char a[] = "kenny"; char b[3]; strcpy(b, a); printf("%s\n", b); 它仍然给我kenny 即使我没有分配足够的内存,你知道发生了什么吗?另外,使用strcpy时,\0会自动添加到dest的末尾吗?
  • 是的,strcpy 附加 \0。但正如我所说,您必须确保目标缓冲区足够大。如果不是(当然 3 对“Kenny”来说不够大),可能会发生奇怪和无法解释的事情。 (正式的定义是“未定义的行为”。)“奇怪和无法解释”肯定包括“即使你没想到它似乎也能工作”。
  • +1:我的建议是完全不要使用strncpy。它的实际作用是不寻常的、特殊的、难以解释的,而且通常不是你想要的。
【解决方案2】:

根据定义,在语句中:

char string[] = "first string"

string 正好填充了它可以容纳的所有内容:

在内存中是这样的:

|f|i|r|s|t| |s|t|r|i|n|g|\0|?|?|?|
/// end of legal memory    ^

...说明以下语句的原因:

strcat(string, anythingElse);

undefined behavior。 (否则被某些人称为 nasal demons。)

另外,关于 strncpy(,,) 的使用。因为不能保证在使用后包含nul 字符,所以建议始终明确地将nul 附加到新字符串中的正确位置:

strncpy (target, source, n);
target[n] = 0;

在您的示例中,n == (sizeof(b) - 1)

请注意,当使用sizeof 作为strncpy(,,*) 的第三个参数的类型为size_t 时,上述表达式中不需要转换为(int)

char *strncpy (char Target_String[], const char Source_String[], size_t Max_Chars);

另一方面,strncat 的用法,确实nul 字符附加到结果目标字符串的末尾,从而无需显式附加nul.

【讨论】:

  • 非常感谢!但是当我做char a[] = "kenny"; char b[] = " confused"; strcat(a, b); printf("%s\n", a); 时,它仍然会打印出kenny confused,所以似乎a 的原始6 个字节被某种方式忽略了
  • @KennyWang - 仔细阅读我留下的描述未定义行为的链接。
  • @chqrlie - 如果您建议我不鼓励使用 r 字符串函数,(由您提供的链接推断。)我不同意作者的观点建议 not 使用r string 函数。我在其他 cmets 中注意到您同意他的观点,但我发现它们在目标字符串的大小固定但要复制的字符串大小未知的许多情况下非常有用。但是,我很欣赏你在这个话题上的立场。谢谢。
  • @ryyker:我用更多自定义函数更新了我的答案stackoverflow.com/a/41885173/4593267,我发现这些函数在我的项目中很有用。
【解决方案3】:

第一个问题

当你这样做时

char string[] = "first string";
            ^
            No size specified

编译器将保留可以准确保存文本“第一个字符串”和 NUL 终止的内存。如果您打印字符串的大小,您将得到 13。换句话说 - 变量可以保存更多数据,因此连接另一个字符串是没有意义的。

你可以这样做:

char string[100] = "first string";

然后你可以连接另一个字符串。

第二个问题

首先要知道的是,C 中的字符串是包含 NUL 终止符的字符数组。

当你这样做时:

char b[3];

你得到一个未初始化的数组,即b 可以包含任何东西——比如b = { ? , ? , ? }

然后你做:

strncpy(b, a, (int)(sizeof(b) - 1));

意味着您将a 的前两个字符复制到b

所以现在我们知道bb = { 'k' , 'e' , ? } 请注意b 的第三个字符仍未初始化。

所以当你这样做时:

printf("string length: %i\n", (int)strlen(b));
printf("%s\n", b);

你使用b 好像它是一个字符串,但它不是。没有 NUL 终止。因此,函数(printfstrlen)给出了不正确的结果。使用 char 数组调用这些函数没有 NUL 终止是未定义的行为,即任何事情都可能发生。

似乎发生了两件事:

a) b 中的未初始化字符恰好是“E”(在您的一个示例中)

b) 字符串文字“kenny”恰好位于内存中变量b 之后。

所以这两个字符串函数确实看到了长度为 8 的字符串“keEkenny”。

要解决此问题,您可以这样做:

strncpy(b, a, (int)(sizeof(b) - 1));
b[sizeof(b) - 1] = '\0';

或者干脆做:

char b[3] = { 0 };

因为这将初始化所有b,即b = { '\0' , '\0' , '\0' }

【讨论】:

    【解决方案4】:

    如果您阅读strncpy 的文档,它会非常清楚地指出,如果您指定的大小不包含 NUL 终止符,它将不会添加它:

    strncpy() 函数类似,不同之处在于最多 n 个字节的 src 复制。警告:如果 src 的前 n 个字节中没有空字节, 放置在 dest 中的字符串不会以 null 结尾。

    因此在以下情况下,您只复制了 2 个字符,并且它们都不是 NUL 终止符,因此您需要自己添加。

    strncpy(b, a, (int)(sizeof(b) - 1));
    

    【讨论】:

      【解决方案5】:

      您必须以某种方式将字符串终止符 \0 添加到 b。 printf("%s\n", b) 将在找到 \0 时停止。

      这取决于您的内存,有时会出现段错误。

      【讨论】:

        猜你喜欢
        • 2021-06-24
        • 1970-01-01
        • 2013-10-04
        • 2017-01-11
        • 2016-08-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多