43

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

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

buffer现在要求相等吗"123\0efghijklmnop"

另一个例子:

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

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

4

7 回答 7

31

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

标准说:

%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 中的空字符后面的字节混淆。这是因为它必须根据这一规范在两种情况下都工作。

因此,由于规范,它必须按照您所说的那样工作

可以为其他函数放置类似的参数,但我认为您可以从这个示例中看到这个想法。

注意:以“%16s”等格式提供大小限制可能会改变行为。根据规范, sscanf 在将数据写入缓冲区之前将缓冲区归零到其限制在功能上是可以接受的。在实践中,大多数实现选择性能,这意味着它们不理会其余部分。

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

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

于 2015-02-25T07:06:52.347 回答
24

Each individual byte in the buffer is an object. Unless some part of the function description of sscanf or fgets mentions modifying those bytes, or even implies their values may change e.g. by stating their values become unspecified, then the general rule applies: (emphasis mine)

6.2.4 Storage durations of objects

2 [...] An object exists, has a constant address, and retains its last-stored value throughout its lifetime. [...]

It's this same principle that guarantees that

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

attempts to print 1 twice. Even though a is global, printf can access global variables, and the description of printf doesn't mention not modifying a.

Neither the description of fgets nor that of sscanf mentions modifying buffers past the bytes that actually were supposed to be written (except in the case of a read error), so those bytes don't get modified.

于 2015-02-25T10:47:48.957 回答
8

标准对此有些模棱两可,但我认为对它的合理解读是答案是:是的,不允许向缓冲​​区写入比读取 + null 更多的字节。另一方面,对文本进行更严格的阅读/解释可能会得出结论,答案是否定的,不能保证。以下是公开可用的草稿中关于fgets.

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

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

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

有一个关于它应该从输入中读取多少的保证,即在换行符或 EOF 处停止读取并且不读取超过n-1字节。尽管没有明确说明允许写入缓冲区的数量,但常识是fgets'n参数用于防止缓冲区溢出。标准使用模棱两可的术语read有点奇怪,如果您想挑剔它使用的术语,这可能不一定意味着gets不能写入缓冲区超过字节。n但请注意,这两个问题使用相同的“读取”术语:n-limit 和 EOF/换行符限制。所以如果你解释n-相关的“读取”作为缓冲区写入限制,然后[为了一致性]您可以/应该以相同的方式解释另一个“读取”,即当字符串短于缓冲区时,写入的内容不会超过读取的内容。

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

在换行符(保留)之后或文件结尾之后,不会将其他字符读入 [到数组中]。

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

我懒得去分析他们关于*scanf家庭的文章,因为我怀疑考虑到这些功能中发生的所有其他事情,情况会更加复杂;他们的fscanf文章大约有五页长……但我怀疑类似的逻辑也适用。

于 2015-02-25T09:38:59.037 回答
4

是否保证缓冲区不会被修改超出空终止符?

不,没有保证。

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

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

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

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

于 2015-02-25T06:51:13.760 回答
1

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

这里buffer只包含123保证在 NUL 处终止的字符串。

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

例如:

char buffer[4096] = "abc";` 

实际上在下面做了一些事情,

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

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

于 2015-02-25T06:28:41.390 回答
0

There are no any guarantees from standard, which is why the functions sscanf and fgets are recommended to be used (with respect to the size of the buffer) as you show in your question (and using of fgets is considered preferable compared with gets).

However, some standard functions use null-terminator in their work, e.g. strlen (but I suppose you ask about string modification)

EDIT:

In your example

fgets(buffer, 10, fp);

untouching characters after 10-th is guaranteed (content and length of buffer will not be considered by fgets)

EDIT2:

Moreover, when using fgets keep in mind that '\n' will be stored in the buffers. e.g.

 "123\n\0fghijklmnop"

instead of expected

 "123\0efghijklmnop"
于 2015-02-25T06:36:13.373 回答
0

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

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

于 2015-02-27T05:34:39.717 回答