提问人: 提问时间:10/23/2019 最后编辑:6 revs, 5 users 65%Lundin 更新时间:12/14/2022 访问量:2728
字符数组应该如何用作字符串?
How should character arrays be used as strings?
问:
我知道 C 中的字符串只是字符数组。所以我尝试了以下代码,但它给出了奇怪的结果,例如垃圾输出或程序崩溃:
#include <stdio.h>
int main (void)
{
char str [5] = "hello";
puts(str);
}
为什么这不起作用?
它使用 .gcc -std=c17 -pedantic-errors -Wall -Wextra
注意:这篇文章旨在用作规范的常见问题解答,以解决在声明字符串时未能为 NUL 终止符分配空间而引起的问题。
答:
C 字符串是以 null 终止符结尾的字符数组。
所有字符都有一个符号表值。null 终止符是符号值(零)。它用于标记字符串的末尾。这是必要的,因为字符串的大小不会存储在任何地方。0
因此,每次为字符串分配空间时,都必须为 null 终止符包含足够的空间。您的示例不会这样做,它只为 的 5 个字符分配空间。正确的代码应该是:"hello"
char str[6] = "hello";
或者等效地,您可以编写 5 个字符和 1 个 null 终止符的自记录代码:
char str[5+1] = "hello";
但你也可以使用它,让编译器进行计数并选择大小:
char str[] = "hello"; // Will allocate 6 bytes automatically
在运行时为字符串动态分配内存时,还需要为 null 终止符分配空间:
char input[n] = ... ;
...
char* str = malloc(strlen(input) + 1);
如果不在字符串末尾附加 null 终止符,则期望字符串的库函数将无法正常工作,并且会出现“未定义行为”错误,例如垃圾输出或程序崩溃。
在 C 中编写空终止符的最常见方法是使用所谓的“八进制转义序列”,如下所示: .这 100% 等同于编写 ,但作为自记录代码,用于声明零明确表示为空终止符。诸如 将检查特定字符是否为 null 终止符的代码。'\0'
0
\
if(str[i] == '\0')
请注意,术语 null 终止符与 null 指针或宏无关!这可能会令人困惑 - 名称非常相似,但含义却截然不同。这就是为什么 null 终止符有时被称为一个 L,不要与 null 指针混淆。有关更多详细信息,请参阅此 SO 问题的答案。NULL
NUL
NULL
代码中的 称为字符串文本。这被视为只读字符串。该语法意味着编译器将自动在字符串文本的末尾附加一个 null 终止符。因此,如果你打印出来,你会得到 6,而不是 5,因为你得到的数组的大小包括一个 null 终止符。"hello"
""
sizeof("hello")
它使用 gcc 干净地编译
事实上,甚至没有警告。这是因为 C 语言中存在一个微妙的细节/缺陷,它允许使用字符串文字初始化字符数组,该字符串文字包含的字符数与数组中的空间完全相同,然后静默丢弃 null 终止符 (C17 6.7.9/15)。由于历史原因,该语言故意这样做,有关详细信息,请参阅字符串初始化的不一致 gcc 诊断。另请注意,C++在这里是不同的,不允许使用此技巧/缺陷。
评论
char *str = "hello";
str[0] = foo;
sizeof
0
0
char[n+1]
摘自 C 标准(7.1.1 术语定义)
1 字符串是以 和 结尾的连续字符序列 包括第一个 null 字符。术语多字节字符串是 有时用来强调给予的特殊处理 字符串中包含的多字节字符或以避免混淆 用宽绳子。指向字符串的指针是指向其初始值的指针 (最低寻址)字符。字符串的长度是 null 字符前面的字节数和字符串的值是 所包含字符的值的顺序。
在本声明中
char str [5] = "hello";
字符串文字的内部表示形式如下"hello"
{ 'h', 'e', 'l', 'l', 'o', '\0' }
所以它有 6 个字符,包括终止零。它的元素用于初始化字符数组,该数组仅为 5 个字符保留空间。str
C 标准(与 C++ 标准相反)允许在字符串文字的终止零不用作初始值设定项时对字符数组进行此类初始化。
但是,因此,字符数组不包含字符串。str
如果你希望数组包含一个字符串,你可以写
char str [6] = "hello";
或者只是
char str [] = "hello";
在最后一种情况下,字符数组的大小由等于 6 的字符串文本的初始值设定项数确定。
是否可以将所有字符串视为字符数组(是),是否可以将所有字符数组视为字符串(否)。
为什么不呢?为什么这很重要?
除了解释字符串的长度不会作为字符串的一部分存储在任何地方以及对定义字符串的标准的引用的其他答案之外,另一面是“C 库函数如何处理字符串?
虽然字符数组可以包含相同的字符,但它只是一个字符数组,除非最后一个字符后面跟着 nul 终止字符。该 nul 终止字符允许将字符数组视为(作为)字符串处理。
C 语言中所有需要字符串作为参数的函数都期望字符序列以 nul 结尾。为什么?
它与所有字符串函数的工作方式有关。由于长度不包含在数组中,因此 string-functions 在数组中向前扫描,直到 nul 字符(例如 -- 等同于十进制 )。请参阅 ASCII 表和说明。无论您是否使用 、、 等。所有字符串函数都依赖于存在的 nul 终止字符来定义该字符串的末尾位置。'\0'
0
strcpy
strchr
strcspn
对两个相似函数的比较将强调 nul 终止字符的重要性。举个例子:string.h
char *strcpy(char *dest, const char *src);
该函数只是将字节从 复制到 ,直到找到 nul 终止字符,告诉在哪里停止复制字符。现在取类似的函数:strcpy
src
dest
strcpy
memcpy
void *memcpy(void *dest, const void *src, size_t n);
该函数执行类似的操作,但不考虑或要求参数为字符串。由于不能简单地向前扫描将字节复制到直到到达 nul 终止字符,因此它需要显式的字节数作为第三个参数进行复制。这第三个参数提供与相同大小的信息能够简单地通过向前扫描来推导,直到找到一个 nul 终止字符。src
memcpy
src
dest
memcpy
strcpy
(这也强调了如果你未能为函数提供以 nul 结尾的字符串,(或任何需要字符串的函数)中会出现什么问题——它不知道在哪里停止,并且会愉快地在调用未定义行为的内存段的其余部分跑开,直到碰巧在内存中的某个地方找到一个 nul 字符——或者发生分段错误)strcpy
这就是为什么需要以 nul 结尾的字符串的函数必须传递以 nul 结尾的字符串以及为什么它很重要。
评论
char
.c_str()
:)
直观。。。
将数组视为变量(保存事物),将字符串视为值(可以放置在变量中)。
它们当然不是一回事。在你的例子中,变量太小,无法容纳字符串,所以字符串被截断了。 (C 中的“带引号的字符串”末尾有一个隐式的 null 字符。
但是,可以将字符串存储在比字符串大得多的数组中。
请注意,通常的赋值和比较运算符(等)并不像您预期的那样工作。但是,一旦你知道自己在做什么,函数系列就非常接近了。请参阅有关字符串和数组的 C 常见问题解答。=
==
<
strxyz
评论