【发布时间】:2013-08-28 19:17:24
【问题描述】:
我见过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's,并且程序不会崩溃。但是如果我尝试写 8 个'a's,它会的。
正如我后来看到的,这样做的原因是,至少在我的系统上,当我分配char buff[7](有或没有="")时,第8个字节(从1开始计数,而不是从0开始)得到设置为 0。据我猜测,事情就是这样完成的,这样一个带有 7 次写入的 for 循环,然后是一个格式化读取的字符串,可以成功,无论要写入的最后一个字符是否为 '\0',从而避免程序员在单独编写字符时自己设置最后一个'\0'。
由此可见
fgets(buff, sizeof(buff), stdin);
然后提供过长的输入,生成的buffstring 将自动包含两个'\0' 字符,一个在数组内,一个在数组后面,由系统写入。
我也观察到这样做
fgets(buff,(sizeof(buff)+17),stdin);
仍然可以工作,并输出一个很长的字符串,而不会崩溃。根据我的猜测,这是因为fgets 会一直写到sizeof(buff)+17,而最后一个要写入的字符将恰好是'\0',确保任何即将进行的字符串读取过程都会正确终止(尽管内存被弄乱了)无论如何都起来了)。
那么,fgets(buff, (sizeof(buff)+1),stdin); 呢?这将用完在buff 中正确分配的所有空间,然后在其后写入'\0',从而覆盖...之前由系统写入的'\0'。换句话说,是的,fgets 会越界,但可以证明,当写长度只加一个时,程序永远不会崩溃。
所以最后,问题来了:为什么fgets 总是以'\0' 终止其写入,而另一个'\0',由系统放置在数组之后,已经存在?为什么不逐一进行基于for-loop 的写入,它可以访问整个数组并写入程序员想要的任何东西,而不会危及任何东西?
非常感谢您的回答!
编辑:确实,没有证据可能,只要我不知道分配 buff[7] 时神秘出现的第 8 个'\0' 是否是 C 标准的一部分,特别是对于字符串数组.如果没有,那么......它只是运气好:-)
【问题讨论】:
-
请仔细考虑,因为某些东西没有崩溃,这意味着它实际上是正确的;通常像这样的错误会导致“未定义的行为”。有时你会得到一个段错误,有时你不会。如果你有 buff[7],则不能保证第 8 个字节是 \0,它可以是任何东西。
-
“我总是会得到 7 个 'a's,并且程序不会崩溃” - 你期望它应该/可能崩溃至少表明你理解 未定义的行为。关于您的问题,因为这就是
fgets的行为方式。如果您有char a;并通过&a的任意大小大于1,您会期望 任何确定性 吗?它是一个 C-api,和大多数一样,它的行为要么有用,要么未定义,具体取决于 您 的调用方式。 -
我了解在我的单机上进行的任何测试都无法证明任何事情。我只是将字符串视为一个数组,您可以在其中编写任何您想要的内容而不考虑最后一个单元格将包含什么(就像您在
int[]中一样),然后将其视为一个字符串,即作为一个词,对缺少的终止字符无所不在的恐惧。因此,标准可能已将第 8 个 '\0' 作为硬设置参数包含在内。因为我不知道标准的细节...我在问这个问题:这第八个'\0'是 C 标准的一部分吗? -
你说:我总会得到7个'a',程序不会崩溃。但是,如果我尝试写 8 'a's,它会。 不幸的是,当你写 7 个 a's 时它不会崩溃,但你正在调用未定义的行为,并且很容易发生崩溃。一个接一个的错误是阴险的,因为它们会让你陷入一种虚假的安全感。您可能会发现在
buff[6]之后的堆栈上有一个未使用的字节(因为下一个变量需要在偶数边界上对齐)。但你不能依赖它...... -
@MrBrody 由于您使用的是 7 字节数组,因此它可能会被填充零。尝试使用一个 8 字节的数组,看看你是否很幸运。当您要求
n字节时,您将得到保证n字节,但您可能得到更多。