更好地理解 strncpy() 函数行为

On better understanding the strncpy() function behavior

在 strncpy 的 Linux 联机帮助页中,我读到:

If the length of src is less than n, strncpy() writes additional
null bytes to dest to ensure that a total of n bytes are written.

在这种极限情况下(两个字符串末尾都没有 \0),其中 n>4:

    char dest[8]="qqqqqqqq";
    char src[4] = "abcd";
    strncpy(dest, src, 5);

按字符打印目标字符得到“abcdqqqq”。 由于 src 中没有 \0,因此没有 \0 从 src 复制到 dest,但如果我对手册页的理解正确,则无论如何都应该复制其他 4 个字符,它们应该是 \0s。 此外,如果 src 是“abc”(因此它以 NUL 结尾),dest 包含“abc[=21=][=21=]qqq”。 我添加了我用来测试的整个代码(是的,它也会到第 8 个字符来查看它):

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

int main()
{
    char dest[8]="qqqqqqqq";
    char src[4] = "abcd"; //  "abc"
    strncpy(dest, src, 5);
    for (int i=0; i<9; i++)
        printf("%2x ", dest[i]);
    putchar('\n');

    return 0;
}

这是一个错误的实现还是我错过了什么?

If the length of src

这就是问题所在,您的 src 不是以空字符结尾的字符串,因此 strncpy 不知道它有多长。联机帮助页还有一个示例代码,显示了 strncpy 的工作原理:它在一个空字节处停止,如果找不到空字节,它会继续读取直到到达 n。在您的情况下,它读取 src 的 4 个字符并从 dest 中获取第五个字符,因为它在内存中是下一个。 (你可以尝试打印 src[4],没有什么能阻止你,你将从 dest 得到 q。C 不是很好吗?)

您使用 strncpy() 时使用的源参数似乎不是字符串,而是字符数组。那么你为什么期望它表现得明智呢? Linux 上的手册清楚地表明它采用字符串作为源参数,并且它复制它“包括终止空字节”(假定这样一个字节)。请注意,没有“non-terminated 字符串”这样的东西。字符数组只是字符数组,字符串函数不能保证对它起作用。

但是,您观察到的特定行为是预期的并记录在案。

The rationale for strncpy() in the POSIX standard包含以下句子:

If there is no NUL character byte in the first n bytes of the array pointed to by s2, the result is not null-terminated.

... 其中 s2 就是您所说的 src.

至少 Ubuntu 和 OpenBSD 上 strncpy() 的手册包含相似的措辞。

让我们从逻辑上看一下,引用 Debian 10 中包含的日期为 2017 年 9 月 15 日的 GNU 手册页:

If the length of src is less than n, strncpy() writes additional null bytes to dest to ensure that a total of n bytes are written.

如您正确陈述(有点),n == 5。那么“src的长度”是多少?众所周知,C 字符串是null-terminated。手册页对此有提示:

The strcpy() function copies the string pointed to by src, including the terminating null byte ('[=21=]') … The strncpy() function is similar, except that at most n bytes of src are copied.

不过您的字符串不是 null-terminated。因此,在读取了您定义的 4 个字节后,strncpy 尝试读取第五个字节,它找到了什么?显然是 dest 的第一个字节(据我所知,实际上这是 implementation-dependent)。它仍然没有找到终止字符串的空值,所以它继续读取吗?不,因为如上所述:

a total of n bytes are written

at most n bytes of src are copied.

因此它将 5 个字节 "abcdq" 复制到 dest

你说:

other 4 characters should be copied in any case, and they should be [=24=]s

这意味着总长度为 8 个字节。你从哪里得到这个?这是 dest 而不是 src 的“长度”(或至少声明的数组长度),并且在任何情况下都会被 [= =17=].

srcnull-terminated的情况很简单。

所以不,执行没有错误

其实你很幸运:

The strings may not overlap

他们在这里做,所以,从技术上讲,任何事情都有可能发生。事实上,对这个问题的评论说 Valgrind 对此发出警告。

and the destination string dest must be large enough to receive the copy. Beware of buffer overruns!

这种对字符串长度的粗心应该是一个警告,在您创建缓冲区溢出之前要格外小心。

另一方面,虽然可能strncpy这样的函数会有错误,但极不可能.标准实施已在各种环境中进行了全面测试。任何明显的错误更有可能是您自己的代码中的错误,或者对您自己的代码缺乏理解,就像这里的情况一样。