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

问题描述 投票:8回答:5

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

#include <stdio.h>

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

为什么不起作用?

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


注:这篇文章旨在作为规范的FAQ,用于因声明字符串时未能为NUL终止符分配空间而引起的问题。

c string c-strings string-literals nul
5个回答
9
投票

C字符串是一个以null终止符结尾的字符数组。

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

因此,每次为字符串分配空间时,必须为空终止符包含足够的空间。您的示例不执行此操作,它仅为"hello"的5个字符分配空间。正确的代码应为:

char str[6] = "hello";

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

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

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

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

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

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

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

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


它可以用gcc干净地编译

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


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库函数如何处理字符串?“

虽然一个字符数组可以容纳相同的字符,但是它只是一个字符数组,除非最后一个字符后跟nul-termination字符。该nul-termination字符使将字符数组视为(视为)字符串。

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

它与所有字符串函数的工作方式有关。由于长度不作为字符串函数的一部分包含在数组中,因此请在数组中向前扫描,直到找到nul-character(例如'\0'-等于十进制0)为止。参见ASCII Table and Description。不管您是否使用strcpystrchrstrcspn等。所有字符串函数都依赖于出现的[[nul-terminated字符来定义该字符串的结尾。

string.h中两个相似函数的比较将强调

nul-terminate

字符的重要性。例如: char *strcpy(char *dest, const char *src);
strcpy函数只是将字节从src复制到dest,直到找到

nul-termination

字符,告诉strcpy在哪里停止复制字符。现在使用类似的函数memcpy void *memcpy(void *dest, const void *src, size_t n);
该函数执行类似的操作,但不考虑或要求src参数为字符串。由于memcpy不能简单地向前扫描src就将字节复制到dest,直到到达

nul-termination

字符为止,因此它需要显式数量的字节作为第三个参数进行复制。第三个参数为memcpy提供了相同的大小信息,strcpy可以通过向前扫描直到找到nul-termination字符来简单地得出。(这也强调了strcpy(或任何期望字符串的函数)出了什么问题,如果您无法为函数提供

nul-terminated

字符串-它不知道在哪里停止并且会很高兴在调用Undefined Behavior的其余内存段中争分夺秒,直到在内存中的某个地方偶然发现nul-character或发生分段错误)
这是

why

函数,期望nul终止的字符串必须传递一个nul终止的字符串和为什么重要

0
投票
直观上...

-2
投票
[在C89(默认为gcc)和C11上工作正常编译:
© www.soinside.com 2019 - 2024. All rights reserved.