6

我观察到 Microsoft 实现的一个有趣的问题strncat。它触及源缓冲区之外的 1 个字节。考虑以下代码:

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>

void main()
{
    char dstBuf[1024];
    char* src = malloc(112);
    memset(src, 'a', 112);
    dstBuf[0] = 0;
    strncat(dstBuf, src, 112);
}

strncat在 112 字节块之后读取 1 个字节。因此,如果您不幸在无效页面边界上获得分配,您的应用程序就会崩溃。大型应用程序可能会在这些地方间歇性地崩溃。(请注意,可以使用gflags PageHeap设置来模拟这种情况;块大小必须能被指针大小整除才能正确对齐。)

这是预期的行为还是错误?有什么链接可以确认吗?(我阅读了一些描述,strncat但可以根据您最初的想法来解释它们的两种方式......)

更新(回答有关证据的问题):如果从上面的文字中不清楚,我很抱歉,但这是一个实验事实。strncat我在读取地址 src+srcBufSize的应用程序中观察到间歇性崩溃。在这个使用gflags PageHeap运行的小示例中,崩溃时始终重现 (100%)。因此,据我所知,证据非常确凿。

Update2(编译器信息)MS Visual Studio 2005 版本 8.0.50727.867。构建平台:64 位版本(32 位无复制)。用于重现崩溃的操作系统:Windows Server 2008 R2。

更新 3使用 MS Visual Studio 2012 11.0.50727.1 中内置的二进制文件也会重现该问题

更新 4 链接以在 Microsoft Connect 上发布链接到 MSDN 论坛上的讨论

更新 5该问题将在下一个 VS 版本中修复。没有计划对旧版本进行修复。请参阅上面的“Microsoft Connect”链接。

4

3 回答 3

3

状态的文档strncat

src - 指向要从中复制的空终止字节字符串的指针

因此,实现可以假设src输入参数实际上是 NUL 终止的,即使它比count字符长。

为了进一步确认,微软自己的文档指出:

strSource

以 Null 结尾的源字符串。

另一方面,实际的 C 标准规定如下:

strncat函数将不超过n所指向的数组中的字符(不附加空字符和其后的字符)附加到 所指向s2的字符串的末尾s1

正如下面的评论所指出的,这将第二个参数标识s2为一个数组,而不是一个以 NUL 结尾的字符串。但是,对于原始问题,这仍然是模棱两可的,因为该文档描述了对 的最终影响s1,而不是从s2.

这当然可以通过查阅 C 运行时库源代码针对特定的Microsoft 实现来解决。

于 2013-08-30T03:48:23.490 回答
2

s2不是的“字符串” strncat(s1, s2, n)

因此,如果 Microsoft 正在读取传递n字节,则它不符合 C11。

C11 7.24.2.3.1strcat()提到“将 s2 指向的字符串
的副本(包括终止空字符)附加到 s1 指向的字符串的末尾”。

C11 7.24.2.3.2strncat说: “strncat 函数从s2 指向的数组
中将不超过 n 个字符(一个空字符和后面的字符不附加)附加到 s1 指向的字符串的末尾。.. . 一个终止的空字符总是附加到结果“

显然,在这种strncat情况下,s2它被视为一个“数组”,对附加到s1. 因此,在连接期间,不需要检查s2超过绝对需要的内容。最后写\0的来自代码,而不是s2.

不知道旧的 C99 标准。

于 2013-09-16T04:35:31.340 回答
1

英语是一种不完美的语言,比 C 语言更不完美。

文档说“最多n 个字符”(我的重点)。没有证据表明 strncat 复制了超过 112 个字符。是什么让你相信它确实如此?

strncat 的代码可能会索引超过 112 的偏移量,但实际上不会引用偏移量 113,这可能会导致存储错误。这种 ptr 行为在 K&R 中被定义为可接受的。

最后,这又是一个英语/推理问题,文档可能确实说空终止字符串。但实际上,说一个字符串是空终止的不是多余的吗?它们是根据定义的,否则它们将是一个字符数组。因此,文档模糊且不具体。程序员可以在字里行间阅读。软件文档不是合法的书籍,它们是旨在由本领域技术人员理解的描述。

于 2013-08-30T04:40:07.933 回答