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

时间:2015-02-25 06:24:23

标签: c standards c-standard-library

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

char buffer[17] = "abcdefghijklmnop";
sscanf("123", "%16s", buffer);

buffer现在需要等于"123\0efghijklmnop"吗?

另一个例子:

char buffer[10];
fgets(buffer, 10, fp);

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

7 个答案:

答案 0 :(得分:31)

C99 draft标准没有明确说明在这些情况下会发生什么,但是通过考虑多种变体,您可以证明它必须以某种方式工作,以便它在所有情况下都符合规范。

标准说:

  

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

     

如果不存在l长度修饰符,则相应的参数应为a   指向大小足以接受的字符数组的初始元素的指针   序列和终止空字符,将自动添加。

以下是一对示例,表明它必须按照您提出的方式运行,以符合标准。

示例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"

例B:

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

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

所以含蓄它必须按照规定工作,因为规范。

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

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

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

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

答案 1 :(得分:23)

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

  

6.2.4对象的存储持续时间

     

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

同样的原则保证

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

尝试打印两次。即使a是全局变量,printf也可以访问全局变量,而printf的说明并未提及而不是修改a

fgets的描述和sscanf的描述都没有提到修改缓冲区超过实际应该写入的字节(除了读取错误的情况),所以那些字节不是&#39 ;得到修改。

答案 2 :(得分:8)

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

  

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

     

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

     

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

可以保证从输入中读取的程度,即停止在换行符或EOF读取而不读取超过n-1个字节。虽然没有明确说明允许写入到缓冲区的程度,但常识是fgets的{​​{1}}参数用于防止缓冲区溢出。标准使用含糊不清的术语 read 有点奇怪,这可能不一定意味着n不能到缓冲区而不是{{1 } bytes,如果你想挑选它使用的术语。但请注意,对于这两个问题使用相同的“读取”术语:gets - 限制和EOF /换行限制。因此,如果您将n - 相关的“读取”解释为缓冲区写入限制,那么[为了一致性]您可以/应该以相同的方式解释另一个“读取”,即不要写出比读取时更多的内容。 string比缓冲区短。

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

  

在新行字符(保留)或文件结束后,没有其他字符读入[数组]。

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

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

答案 3 :(得分:4)

  

保证缓冲区不会被修改为超出null   端子?

不,不能保证。

  

现在需要缓冲区等于“123 \ 0efghijklmnop”吗?

是。但这只是因为你已经在字符串相关函数中使用了正确的参数。如果你搞乱缓冲区长度,输入修饰符到sscanf等等,那么程序将编译。但它很可能在运行时失败。

  

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

是。一旦fgets()表示您有一个3个字符的输入字符串,它就会将输入存储在提供的缓冲区中,并且它根本不关心所提供空间的重置。

答案 4 :(得分:1)

  

现在需要缓冲区等于“123 \ 0efghijklmnop”吗?

此处buffer只包含123字符串,保证在NUL终止。

是的,为数组buffer分配的内存不会被取消分配,但是你要确保/限制你的字符串buffer最多只能有16个char元素你可以阅读在任何时间进入它。现在取决于您是只编写一个字符还是最大buffer可以使用的字符。

例如:

char buffer[4096] = "abc";` 

实际上做了以下事情,

memcpy(buffer, "abc", sizeof("abc"));
memset(&buffer[sizeof("abc")], 0, sizeof(buffer)-sizeof("abc"));

标准坚持认为,如果char数组的任何部分被初始化,那么它在任何时刻都包含它,直到服从它的内存边界。

答案 5 :(得分:0)

标准没有任何保证,这就是为什么建议使用函数sscanffgets(关于缓冲区的大小),如你在问题中所示(和与fgets相比,gets的使用被认为是优选的。

但是,某些标准函数在其工作中使用null-terminator,例如strlen(但我想你问一下字符串修改)

修改

在你的例子中

fgets(buffer, 10, fp);
保证第10个字符后保持不变的字符(buffer不会考虑fgets的内容和长度)

<强> EDIT2:

此外,使用fgets时请记住'\n'将存储在缓冲区中。 e.g。

 "123\n\0fghijklmnop"

而非预期

 "123\0efghijklmnop"

答案 6 :(得分:0)

取决于使用中的功能(以及在较小程度上的实现)。 sscanf会在遇到第一个非空白字符时开始写入,并继续写入直到它的第一个空白字符,在那里它将添加一个完成0并返回。但像strncpy这样的函数(着名的)将剩余的缓冲区清零。

然而,C标准中没有规定这些函数的行为方式。