C标准保证缓冲区是否触及它们的空终止符?
在为标准库的许多string函数提供缓冲区的各种情况下,是否保证缓冲区不会被修改超出空终止符? 例如:
char buffer[17] = "abcdefghijklmnop"; sscanf("123", "%16s", buffer);
buffer
现在是否需要等于"123\0efghijklmnop"
?
另一个例子:
char buffer[10]; fgets(buffer, 10, fp);
如果读取的行只有3个字符,可以确定第6个字符与fgets被调用之前是一样的吗?
缓冲区中的每个单独的字节都是一个对象。 除非sscanf
或fgets
的函数描述的某些部分提到修改这些字节,或者甚至暗示它们的值可能改变,例如通过声明它们的值变得没有指定,那么一般规则适用于:(强调我的)
6.2.4对象的存储时间
2 […]存在一个对象,具有一个常量地址, 并在其整个生命周期中保留其最后存储的值 。 […]
这是相同的原则,保证
#include <stdio.h> int a = 1; int main() { printf ("%d\n", a); printf ("%d\n", a); }
试图打印两次。 即使a
是全局的, printf
也可以访问全局variables, printf
的描述没有提到不修改a
。
fgets
的描述和sscanf
的描述都没有提到修改缓冲区超过实际上应该写入的字节(除非发生读错误),所以这些字节不会被修改。
C99标准草案没有明确说明在这些情况下应该发生什么,但是考虑到多种变化,你可以certificate它必须以某种方式工作,以便在所有情况下符合规范。
标准说:
%s – 匹配一系列非空白字符.252)
如果不存在l长度修饰符,则相应的参数应该是一个指向字符数组的初始元素的指针,该字符数组的大小足以接受该序列和一个终止的空字符,该字符将被自动添加。
这里有两个例子表明它必须按照你提出的符合标准的方式工作。
例子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中的空字符之后的字节。这是因为它必须根据规范的这一点在两种情况下工作。
所以它隐含地必须按照你所说的那样工作。
其他function也可以用类似的参数,但是我想你可以从这个例子中看到这个想法。
注:提供格式的大小限制(如“%16s”) 可能会改变行为。 根据规范,在将数据写入缓冲区之前,sscanf将缓冲区清零至其极限在function上是可接受的。 实际上,大多数实现select性能,这意味着他们只剩下剩下的部分。
当规范的意图是做这种调零的时候,通常是明确指定的。 strncpy就是一个例子。 如果string的长度小于指定的最大缓冲区长度,则会用空字符填充空格的其余部分。 事实上,这个相同的“string”函数可能会返回一个非终止的string,这使得人们最常用的function之一,滚动自己的版本。
就fgets而言,可能会出现类似的情况。 唯一的问题是规范明确规定,如果没有读入,缓冲区保持不变。 一个可接受的function实现可以通过在清零缓冲区之前检查是否至less有一个字节要读取来回避这个问题。
这个标准有点含糊不清,但我认为合理的解释是答案是:是的,不允许向缓冲区写入比读取+ null更多的字节。 另一方面,对文本的更严格的阅读/解释可能会得出结论:答案是否定的,不能保证。 以下是关于fgets
的公开可用的草案 。
char *fgets(char * restrict s, int n, FILE * restrict stream)
;
fgets
函数最多只读取一个比从stream指向的stream中指定的字符数less一个字符到s
指向的数组。 换行符(保留)或文件结束后不会读取其他字符。 在读入数组的最后一个字符之后立即写入一个空字符。如果成功,
fgets
函数将返回s
。 如果遇到文件结束并且没有字符被读入数组,则数组的内容保持不变并且返回空指针。 如果在操作期间发生读取错误,则数组内容不确定,并返回空指针。
有一个关于它应该从input读取多less的保证,即停止在换行符或EOF读取,并且不能读取多于n-1
个字节。 虽然没有什么可以明确地说明它允许写入缓冲区的数量,但常识是fgets
的n
参数被用来防止缓冲区溢出。 有一点奇怪的是,这个标准使用了模糊的术语read ,如果你想对它使用的术语进行挑选,这可能不一定意味着gets
不能写入超过n
个字节的缓冲区。 但请注意,关于这两个问题使用了相同的“读取”术语: n
-limit和EOF / newline限制。 所以,如果你将n
相关的“读”解释为缓冲区写限制,那么[为了一致性],你可以/应该以相同的方式解释另一个“读”,即不要超过string短于缓冲区。
另一方面,如果区分短语动词“读入”(=“写入”)和“读取”的用法,则不能以相同的方式读取委员会的文本。 你可以保证它不会“读入”(=“写入”)超过n
个字节的数组,但是如果inputstring被换行符或EOF更早地终止,那么只能保证input的其余部分将不会被“读”,但是否意味着将不被“读入”(=“写入”)在这个更严格的阅读下缓冲区是不清楚的。 关键的问题是关键字“进”,这是被忽略的,所以问题是我在括号中给出的修改后的引用是否是预期的解释:
在换行符(保留)或文件结束后,没有其他字符被读入数组中。
坦白地说,一个单一的后置条件 (在这种情况下会很短)将比我所引用的措辞更有帮助…
我不想去尝试和分析他们关于*scanf
家族的文章,因为我怀疑在这些function中发生的所有其他事情会变得更加复杂; 他们写的fscanf
大约五页长…但我怀疑类似的逻辑适用。
是否保证缓冲区不会被修改超出空终止符?
不,没有保证。
缓冲区现在是否需要等于“123 \ 0efghijklmnop”?
是。 但那只是因为你已经使用了正确的参数来处理string相关的函数。 如果你搞砸缓冲区的长度,input修改器sscanf
等,那么你的程序将编译。 但是它在运行时很可能会失败。
如果读取的行只有3个字符,可以确定第6个字符与fgets被调用之前是一样的吗?
是。 一旦fgets()
数字你有一个3个字符的inputstring,它将input存储在提供的缓冲区,它根本不关心提供的空间的重置。
缓冲区现在是否需要等于“123 \ 0efghijklmnop”?
这里的buffer
是由123
保证终止在NULstring。
是的,为数组buffer
分配的内存不会被取消分配,但是您要确保/限制您的stringbuffer
最多只能有16
字符元素,您可以在任何时间读入它。 现在取决于你是只写一个char还是最大可以使用什么buffer
。
例如:
char buffer[4096] = "abc";`
实际上做了一些事情,
memcpy(buffer, "abc", sizeof("abc")); memset(&buffer[sizeof("abc")], 0, sizeof(buffer)-sizeof("abc"));
标准强调,如果字符数组的任何部分都被初始化了,那么它就是在任何时刻都一直服从它的内存边界。
标准没有任何保证,这就是为什么函数sscanf
和fgets
被推荐使用(关于缓冲区的大小),就像你在问题中显示的那样(并且使用fgets
被认为比fgets
更好)。
然而,一些标准函数在他们的工作中使用了null-terminator,例如strlen
(但是我想你问一下string修改)
编辑:
在你的例子中
fgets(buffer, 10, fp);
保证10个字符以后的字符不变( fgets
不会考虑buffer
内容和长度)
EDIT2:
而且,当使用fgets
请记住'\n'
将被存储在缓冲区中。 例如
"123\n\0fghijklmnop"
而不是预期的
"123\0efghijklmnop"
取决于使用中的function(在较低的程度上它的实现)。 当sscanf
遇到第一个非空白字符时,它将开始写入,并继续写入,直到它的第一个空白字符,它将添加一个结束符0
并返回。 但是像strncpy
(着名)这样的函数strncpy
缓冲区的其余部分strncpy
。
然而,在C标准中没有规定这些function如何performance的内容。