字符数组应如何用作字符串?


10

我知道C中的字符串只是字符数组。因此,我尝试了以下代码,但给出了奇怪的结果,例如垃圾输出或程序崩溃:

#include <stdio.h>

int main (void)
{
  char str [5] = "hello";
  puts(str);
}

为什么不起作用?

它可以用干净地编译gcc -std=c17 -pedantic-errors -Wall -Wextra


注意:对于在声明字符串时未能为NUL终止符分配空间而引起的问题,本帖子旨在用作规范的FAQ。

Answers:


12

AC字符串是一个以空终止符结尾的字符数组。

所有字符都有符号表值。空终止符是符号值0(零)。它用于标记字符串的结尾。这是必需的,因为字符串的大小不会存储在任何地方。

因此,每次为字符串分配空间时,都必须为空终止符包含足够的空间。您的示例没有这样做,它仅为的5个字符分配空间"hello"。正确的代码应为:

char str[6] = "hello";

或者等效地,您可以编写5个字符加1个空终止符的自文档代码:

char str[5+1] = "hello";

在运行时为字符串动态分配内存时,还需要为null终止符分配空间:

char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);

如果不在字符串末尾附加空终止符,则期望字符串的库函数将无法正常工作,并且会出现“未定义行为”错误,例如垃圾输出或程序崩溃。

在C中写空终止符的最常见方法是使用所谓的“八进制转义序列”,如下所示:'\0'。这100%等价于write 0,但是\用作自我说明代码,指出零明确表示是空终止符。诸如的代码if(str[i] == '\0')将检查特定字符是否为空终止符。

请注意,术语空终止符与空指针或NULL宏无关!这可能会令人困惑-名称非常相似,但含义却截然不同。这就是为什么空终止符有时被称为NUL一个L,不要与NULL空指针或空指针混淆。有关更多详细信息,请参见此SO问题的答案。

"hello"你的代码被称为字符串文字。这将被视为只读字符串。该""语法意味着编译器将自动在字符串文字的末尾附加一个空终止符。因此,如果打印出来sizeof("hello"),将得到6,而不是5,因为得到的数组大小包括空终止符。


用gcc干净地编译

确实,甚至没有警告。这是由于C语言中的一个细微的细节/缺陷,它允许使用字符串文字初始化字符数组,该字符串文字包含的字符与数组中的空间一样多,然后静默丢弃空终止符(C17 6.7.9 / 15)。由于历史原因,该语言的行为方式故意如此,有关详细信息,请参见字符串初始化的gcc诊断不一致。还要注意,C ++在这里有所不同,并且不允许使用此技巧/缺陷。


1
您应该提及char str[] = "hello";情况。
Jabberwocky

@Jabberwocky这是一个社区Wiki,可以随时进行编辑和贡献。
隆丁

1
...也许还有char *str = "hello";... str[0] = foo;问题。
Jabberwocky

也许将sizeofuse 的含义扩展到对函数参数的使用,尤其是当定义为数组时。
风向标

@WeatherVane应该在此处包含另一个常见问题解答:stackoverflow.com/questions/492384/…–
Lundin

4

根据C标准(7.1.1术语定义)

1 字符串是由第一个空字符终止并包括第一个空字符的连续字符序列。有时使用多字节字符串一词来强调对字符串中包含的多字节字符进行的特殊处理,或避免与宽字符串混淆。指向字符串的指针是指向其初始(最低寻址)字符的指针。字符串的长度是空字符之前的字节数,而字符串的值依次是所包含字符的值的顺序。

在此声明中

char str [5] = "hello";

字符串文字"hello"具有内部表示形式,例如

{ 'h', 'e', 'l', 'l', 'o', '\0' }

因此它有6个字符,包括结尾的零。它的元素用于初始化字符数组str,该数组仅保留5个字符的空间。

当字符串文字的结尾零不用作初始化程序时,C标准(与C ++标准相反)允许对字符数组进行此类初始化。

但是结果是字符数组str不包含字符串。

如果您希望数组包含一个字符串,则可以编写

char str [6] = "hello";

要不就

char str [] = "hello";

在最后一种情况下,字符数组的大小由等于6的字符串文字的初始值设定项确定。


0

是否可以将所有字符串都视为字符数组),可以将所有字符数组都视为字符串)。

为什么不?为何重要?

除了解释字符串长度不作为字符串一部分存储在任何地方以及定义字符串的标准的引用的其他答案外,另一面是“ C库函数如何处理字符串?”。

虽然一个字符数组可以容纳相同的字符,但它只是一个字符数组,除非最后一个字符后面跟有以零结尾的字符。该零终止字符允许将字符数组视为(视为)字符串。

C中所有希望将字符串作为参数的函数都希望字符序列为nul终止的为什么?

它与所有字符串函数的工作方式有关。由于长度不包含在字符串函数数组的一部分中,因此请在数组中向前扫描,直到找到nul字符(例如'\0'-等于decimal 0)为止。请参阅ASCII表和说明。无论您正在使用strcpystrchrstrcspn,等所有的字符串函数依赖NUL终止字符存在,以限定该字符串的结尾。

通过比较两个相似的函数,string.h可以强调终止符的重要性。举个例子:

    char *strcpy(char *dest, const char *src);

strcpy函数只是将字节从复制src到,dest直到找到nul终止字符,告诉strcpy停止在哪里复制字符。现在采取类似的功能memcpy

    void *memcpy(void *dest, const void *src, size_t n);

该函数执行类似的操作,但不考虑或要求src参数为字符串。由于memcpy不能简单地向前src复制字节dest直到到达n 终止字符,因此需要显式数量的字节作为第三个参数进行复制。第三个参数提供memcpy相同的大小信息,该信息strcpy可以简单地通过向前扫描直到找到n 终止字符来得出。

(这也强调了strcpy如果您无法为函数提供以nul终止的字符串,则该函数(或任何期望字符串的函数)出什么问题–它不知道在哪里停止,并且会在内存段的其余部分中愉快地竞争调用未定义的行为,直到恰好在内存中的某个位置找到nul个字符 -或发生分段错误)

这就是为什么函数期待一个空终止字符串必须通过一个空终止字符串,它为什么重要


0

凭直觉...

将数组视为变量(包含事物),将字符串视为值(可以放置在变量中)。

他们当然不是同一回事。在您的情况下,变量太小而无法容纳字符串,因此字符串被切断。(C中的“带引号的字符串”在末尾具有隐式的空字符。)

但是,可以将字符串存储在比字符串大得多的数组中。

请注意,通常的赋值和比较运算符(= == <等)无法正常工作。但是strxyz,一旦您知道自己在做什么,函数家族就会非常接近。请参阅有关字符串数组C FAQ

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.