C 可变长度字符串

4

我有一个疑问,数组的长度是如何分配的

#include <stdio.h>
#include <string.h>

int main()
{
    char str[] = "s";
    long unsigned a = strlen(str);
    scanf("%s", str);
    printf("%s\n%lu\n", str, a);
    return 0;
}

在上面的程序中,我将字符串"s"赋值给了一个char数组。我认为str[]的长度是1,所以我们不能存储超过数组长度的内容。但实际上情况并非如此。如果我使用scanf读取一个字符串,则该字符串可以在没有任何错误的情况下存储在str[]中。那么数组str的长度是多少?
样例输入/输出:
Hello

Hello 1

9
这是未定义行为,可能导致难以跟踪的错误,所以不要越界。 - klutt
1
今天可能没有错误,但明天就会有。如果你超速行驶,不一定会造成事故,只有在撞到东西时才会。在这里,你的越界字符串还没有撞到任何东西。 - Weather Vane
2
如果您的代码存在未定义行为,崩溃是第二好的情况。最好的情况是编译器会报错或警告,但在许多情况下,C编译器对此类问题不提供任何诊断。如果您超出缓冲区的末尾,可能会发生更糟糕的事情:任意代码执行,即您的代码中存在严重的安全漏洞,数据完整性受损,数据保密性... - Hulk
关于 scanf("%s", str); 代码的提示:1) 必须始终检查返回值(而不是参数值),以确保操作成功。请注意,scanf() 函数族会返回成功的输入格式转换说明符数量(或 EOF)。在当前情况下,任何返回值都不等于 1,都表示发生了错误。2) 请注意,缓冲区 str[] 的长度为 2。当使用说明符 %s 和/或 %[...] 时,请始终包括一个 MAX CHARACTERS 修改器,该修改器比输入缓冲区的长度少 1,因为这些说明符总是追加一个 NUL 字节。 - user3629249
这也避免了缓冲区溢出的可能性以及随之产生的未定义行为。 - user3629249
3个回答

6

你的str是一个由char组成的数组,使用"s"初始化,也就是说,它的大小为2,长度为1。大小比长度多一个单位,因为在末尾添加了一个NUL字符串终止字符(\0)。

你的str数组最多可以容纳两个char。尝试写入更多会导致程序访问超出数组末尾的内存,这是未定义的行为。

然而实际上发生的是,由于str数组存储在内存中的某个位置(在堆栈上),并且该内存区域远大于2字节,因此你实际上可以在结尾后继续写入而不会导致崩溃。但这仍然是未定义的行为,不建议这样做。

由于数组的大小为2,它只能容纳长度为1的字符串及其终止符。为了使用scanf()并正确避免写入数组末尾的内存,可以使用字段宽度指定符:在%s之间加上数字值,如下所示:

scanf("%1s", str);

4

str 数组大小为 2:一个字节用于字符 's',另一个字节用于终止空字节。你正在写入数组末尾之外的位置,这样做会导致未定义行为

当你的代码具有未定义行为时,它可能会崩溃,可能会输出奇怪的结果,或者(在本例中)看起来正常工作。此外,在代码中做一个看似无关的更改,比如使用 printf 进行调试或添加一个未使用的局部变量,都可能会影响未定义行为的表现方式。


4

当声明数组时没有指定其大小,而是通过使用的初始化程序确定大小。

在这个数组的声明中

char str[] = "s";

使用字符串字面值作为初始化器。字符串字面值是由一个包含的零终止字符终止的一系列字符。这意味着字符串字面值"s"有两个字符{'s', '\0'}

它的字符用来按顺序初始化数组str的元素。

因此,如果您编写:

printf( "sizeof( str ) = %zu\n", sizeof( str ) );

那么输出结果将是2。字符串的长度是指终止零字符之前的字符数。因此,如果您写的是

#include <string.h>
//...
printf( "strlen( str ) = %zu\n", strlen( str ) );

如果你尝试在数组外写入数据,那么你将会得到未定义的行为,因为属于数组之外的内存将被覆盖。在某些情况下,你可能会得到预期的结果。在其他情况下,程序可能会异常结束。这就是程序的未定义行为。

然后输出将会是1


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接