C标准保证缓冲区是否未触及空终止符?

Seg*_*ted 43 c standards c-standard-library

在为标准库的许多字符串函数提供缓冲区的各种情况下,是否保证缓冲区不会被修改超出null终止符?例如:

char buffer[17] = "abcdefghijklmnop";
sscanf("123", "%16s", buffer);
Run Code Online (Sandbox Code Playgroud)

是否buffer现在应等于"123\0efghijklmnop"

另一个例子:

char buffer[10];
fgets(buffer, 10, fp);
Run Code Online (Sandbox Code Playgroud)

如果读取行只有3个字符长,那么可以确定第6个字符与调用fgets之前相同吗?

cav*_*man 31

C99草案标准中没有明确说明什么应该发生在这些情况下,但考虑多种变化,你可以证明其工作必须以一定的方式使之符合在所有情况下的规范.

标准说:

%s - 匹配一系列非空白字符.252)

如果不存在l length修饰符,则相应的参数应该是指向大小足以接受序列的字符数组的初始元素的指针以及将自动添加的终止空字符.

这是一对示例,表明它必须以您提出的方式满足标准.

例A:

char buffer[4] = "abcd";
char buffer2[10];  // Note the this could be placed at what would be buffer+4
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer =  "123\0"
//           buffer2 = "4\0"
Run Code Online (Sandbox Code Playgroud)

例B:

char buffer[17] = "abcdefghijklmnop";
char* buffer2 = &buffer[4];
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer = "123\04\0"
Run Code Online (Sandbox Code Playgroud)

请注意,sscanf的界面没有提供足够的信息来真正知道这些是不同的.因此,如果示例B要正常工作,它必须不会弄乱示例A中的空字符后的字节.这是因为它必须根据这一点规范在两种情况下都有效.

所以隐含必须按照您说,由于规格.

类似的参数可以放在其他函数中,但我认为你可以从这个例子中看到这个想法.

注意:提供格式的大小限制(例如"%16s")可能会更改行为.根据规范,在将数据写入缓冲区之前,sscanf将缓冲区归零到其极限是功能上可接受的.在实践中,大多数实现都选择性能,这意味着他们只留下剩下的部分.

当规范的目的是进行这种归零时,通常会明确指定.strncpy就是一个例子.如果字符串的长度小于指定的最大缓冲区长度,则它将使用空字符填充剩余的空间.事实上,这个相同的"字符串"函数也可以返回一个非终止字符串,这使得这是人们推出自己版本的最常见功能之一.

至于fgets,可能会出现类似的情况.唯一的问题是规范明确规定如果没有读入任何内容,缓冲区保持不变.可接受的功能实现可以通过检查在清零缓冲区之前是否至少读取一个字节来回避这个问题.


小智 24

缓冲区中的每个字节都是一个对象.除非某些部分功能描述sscanffgets提及修改这些字节,或者甚至暗示它们的值可能会改变,例如通过声明它们的值变得不明确,那么一般规则适用:(强调我的)

6.2.4对象的存储持续时间

2 [...]存在一个对象,具有一个常量地址,并在其整个生命周期内保留其最后存储的值.[...]

正是这个原则保证了这一点

#include <stdio.h>
int a = 1;
int main() {
  printf ("%d\n", a);
  printf ("%d\n", a);
}
Run Code Online (Sandbox Code Playgroud)

试图打印1次两次.即使a是全局的,printf也可以访问全局变量,并且printf不提及修改的描述a.

既没有描述fgets也没有sscanf提及修改缓冲区超过实际应该写入的字节(除了读取错误的情况),因此这些字节不会被修改.

  • @caveman没有授予`fgets`权限来更改除明确指定要修改的字节之外的任何字节这一事实意味着`fgets`的这种实现将无法符合.在抽象机器中,`fgets`不会改变那些字节,并且这些字节的值不会变得不确定,未指定或未定义.因此,具体实现必须保持将这些字节保留在其最后存储的值的行为.如果您不同意,那么您对我的答案中的示例有何看法?这是允许打印"1 \n2 \n"`?如果没有,为什么不呢? (4认同)
  • 这是一个很好的论据,我同意.我认为你的评论比答案本身更清楚.我在你的回答中遗漏的要点是你正在考虑每个字节一个对象.因此,他们没有变得不确定,未指明或定义的声明确实澄清了你在说什么. (2认同)
  • @RespawnedFluff标准附加了一个脚注,说"在易失性对象的情况下,最后一个商店不需要在程序中明确.".这个脚注得到了描述`volatile`(6.7.3p6)的规范性文本的支持.但在OP的情况下,没有易失性对象,所以这不是问题. (2认同)

Fiz*_*izz 8

标准在这方面有些含糊不清,但我认为合理解读它的答案是:是的,不允许向缓冲​​区写入比读取+ null更多的字节.另一方面,对文本的更严格的阅读/解释可以得出结论,答案是否定的,不能保证.这是一份公开的草案所说的内容fgets.

char *fgets(char * restrict s, int n, FILE * restrict stream);

fgets函数最多读取一个小于n指向的数据流指定的字符数所指向stream的数组s.在换行符(保留)或文件结束后不会读取其他字符.在读入数组的最后一个字符后立即写入空字符.

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

可以保证从输入读取多少内容,即在换行符或EOF处停止读取,而不是读取超过n-1字节.虽然不对此是多么的允许明确表示缓冲区,常识是fgetsn参数是用来防止缓冲区溢出.标准使用模糊术语读取有点奇怪,如果你想挑选它使用的术语,这可能不一定意味着gets不能写入超过n字节的缓冲区.但请注意,对于这两个问题使用相同的"读取"术语:n-limit和EOF /换行限制.因此,如果您将n相关的"读取" 解释为缓冲区写入限制,那么[为了一致性]您可以/应该以相同的方式解释另一个"读取",即,当字符串比字符串短时,不要写入比读取更多的内容.缓冲.

另一方面,如果你区分短语动词"read into"(="write")和just"read"的使用,那么你就无法以同样的方式阅读委员会的文本.你保证它不会"读入"(="写入")数组超过n字节,但如果输入字符串被换行或EOF更快终止,你只能保证其余(输入)赢了不是"读",但是这意味着是否会"读入"(="写入")缓冲区在这个更严格的读数下不清楚.关键问题是关键字是"进入",这是被忽略的,所以问题在于我在下面的修改引用中括号中给出的完成是否是预期的解释:

在换行符(保留)或文件结束后,不会在[数组]中读取其他字符.

坦率地说,一个后置条件作为公式表示(在这种情况下会很短)会比我引用的措辞更有帮助...

我不会费心去尝试分析他们关于*scanf家庭的文章,因为我怀疑,考虑到这些功能中发生的所有其他事情,它会变得更加复杂; 他们的写作fscanf长约五页......但我怀疑类似的逻辑适用.