1

我已经看到了几个这样的用法fgets(例如,here):

char buff[7]="";

(...)

fgets(buff, sizeof(buff), stdin);

有趣的是,如果我提供像“aaaaaaaaaaa”这样的长输入,fgets将在此处将其截断为“aaaaaa”,因为第 7 个字符将用于存储'\0'.

但是,这样做时:

int i=0;
for (i=0;i<7;i++)
{
    buff[i]='a';
}
printf("%s\n",buff);

我总是会得到 7'a'秒,程序不会崩溃。但是如果我尝试写 8 'a's,它会的。

正如我后来看到的那样,这样做的原因是,至少在我的系统上,当我分配char buff[7](有或没有="")时,第 8 个字节(从 1 开始计数,而不是从 0 开始计数)被设置为 0。据我猜测,事情就是这样完成的,这样一个for有 7 次写入的循环,然后是一个格式化为读取的字符串,无论最后一个要写入的字符是否是,都可以成功,'\0'从而避免程序员设置最后一个 '\0 ' 他自己,在单独编写字符时。

由此可知,在

fgets(buff, sizeof(buff), stdin);

然后提供太长的输入,生成的buff字符串将自动包含两个'\0'字符,一个在数组内,一个在它之后由系统写入。

我还观察到这样做

fgets(buff,(sizeof(buff)+17),stdin);

仍然可以工作,并输出一个很长的字符串,而不会崩溃。根据我的猜测,这是因为fgets将一直写入直到sizeof(buff)+17,并且要写入的最后一个字符将恰好是 a '\0',从而确保任何即将进行的字符串读取过程都将正确终止(尽管无论如何内存都被弄乱了)。

但是,那又如何fgets(buff, (sizeof(buff)+1),stdin);呢?这将用完所有在 中正确分配的空间,buff然后'\0'在它之后写一个,从而覆盖......'\0'先前由系统写入的空间。换句话说,是的,fgets会越界,但可以证明,当写长度只加一个时,程序永远不会崩溃。

所以最后,问题来了:为什么fgets总是用 a 终止它的写入'\0',而另一个'\0'由系统放置在数组之后的已经存在?为什么不喜欢逐个for循环的写入,它可以访问整个数组并编写程序员想要的任何东西,而不会危及任何东西?

非常感谢您的回答!

编辑:确实,没有证据可能,只要我不知道'\0'在分配 buff[7] 时神秘出现的第 8 个是否是 C 标准的一部分,特别是对于字符串数组。如果没有,那么......它只是运气好:-)

4

2 回答 2

3

但可以证明,当写长度只加1时,程序永远不会crash。

不!你不能证明!不是在数学证明的意义上。您只展示了在您的系统上,使用您的编译器,使用您使用的那些特定编译器设置,使用特定环境配置,它可能不会崩溃。这远非数学证明!

事实上,C 标准本身,虽然它保证您可以获得“数组最后一个元素之后的一个位置”的地址,但它也声明取消引用该地址(即尝试从该地址读取或写入)是未定义的行为.

这意味着在这种情况下,实现可以做所有事情。它甚至可以用幼稚的推理来做你所期望的(即工作 - 但它纯粹是运气),但它也可能会崩溃或者它也可能会格式化你的 HD(如果你非常非常不走运)。在编写系统软件(例如设备驱动程序或在裸机上运行的程序)时尤其如此,即当没有操作系统可以保护您免受编写不良代码的最恶劣后果时!

编辑这应该回答评论中提出的问题(C99标准草案):

7.19.7.2 fgets 函数

概要

#include <stdio.h>
char *fgets(char * restrict s, int n,
    FILE * restrict stream);

描述

fgets 函数从 stream 指向的流中最多读取比 n 指定的字符数少 1 的字符到 s 指向的数组中。在换行符(保留)之后或文件结尾之后不会读取其他字符。在读入数组的最后一个字符之后立即写入一个空字符。

退货

如果成功, fgets 函数将返回 s。如果遇到文件结尾并且没有字符被读入数组,则数组的内容保持不变并返回一个空指针。如果在操作过程中发生读取错误,则数组内容是不确定的,并返回一个空指针。

编辑:由于似乎问题在于对字符串是什么的误解,这是标准的相关摘录(强调我的):

7.1.1 术语定义

字符串是由第一个空字符终止并包括在内的连续字符序列。术语多字节字符串有时用于强调对字符串中包含的多字节字符进行特殊处理或避免与宽字符串混淆。指向字符串的指针是指向其初始(最低地址)字符的指针。字符串的长度是空字符之前的字节数,字符串的值是包含的字符值的顺序,按顺序排列。

于 2013-08-28T19:36:18.493 回答
2

来自 C11 标准草案:

fgets函数 从 stream 指向的流中读取最多比 n 指定的字符数少 1 的字符到 s 指向的数组中在换行符(保留)或文件结尾之后不会读取其他字符。在读入数组的最后一个字符之后立即写入一个空字符。

如果成功, fgets 函数将返回 s。如果遇到文件结尾并且没有字符被读入数组,则数组的内容保持不变并返回一个空指针。如果在操作过程中发生读取错误,则数组内容是不确定的,并返回一个空指针。

您描述的行为未定义。

于 2013-08-28T19:45:20.730 回答