为什么strncpy不为null终止?

为什么strncpy不为null终止?,c,strncpy,C,Strncpy,strncpy()可以防止缓冲区溢出。但如果它在没有null终止的情况下防止溢出,则很可能后续的字符串操作将溢出。因此,为了防止这种情况,我发现自己在做: strncpy( dest, src, LEN ); dest[LEN - 1] = '\0'; man strncpy给出: strncpy()函数类似,只是复制的src字节数不超过n。因此,如果src的第一个n字节中没有空字节,结果将不会以空结束 没有任何看似无辜的东西,比如: printf( "FOO: %s\n&q

strncpy()
可以防止缓冲区溢出。但如果它在没有null终止的情况下防止溢出,则很可能后续的字符串操作将溢出。因此,为了防止这种情况,我发现自己在做:

strncpy( dest, src, LEN );
dest[LEN - 1] = '\0';

man strncpy
给出:

strncpy()
函数类似,只是复制的
src
字节数不超过
n
。因此,如果
src
的第一个
n
字节中没有空字节,结果将不会以空结束

没有任何看似无辜的东西,比如:

   printf( "FOO: %s\n", dest );
…可能会崩溃



是否有比strncpy()更好、更安全的替代方法?

strncpy直接使用可用的字符串缓冲区,如果您直接使用内存,您现在必须使用缓冲区大小,并且可以手动设置“\0”


我相信在普通C中没有更好的替代方案,但如果您在使用原始内存时尽可能小心的话,它并没有那么糟糕。

ISO/IEC TR 24731(检查信息)中指定了一些新的替代方案。大多数函数都采用一个附加参数,该参数指定目标变量的最大长度,确保所有字符串都以null结尾,并且名称以
\u s
(表示“安全”)结尾,以区别于早期的“不安全”版本。1

不幸的是,它们仍在获得支持,并且可能无法与您的特定工具集一起使用。如果使用旧的不安全函数,Visual Studio的更高版本将抛出警告

如果您的工具不支持新函数,那么为旧函数创建自己的包装器应该相当容易。下面是一个例子:

errCode_t strncpy_safe(char *sDst, size_t lenDst,
                       const char *sSrc, size_t count)
{
    // No NULLs allowed.
    if (sDst == NULL  ||  sSrc == NULL)
        return ERR_INVALID_ARGUMENT;

   // Validate buffer space.
   if (count >= lenDst)
        return ERR_BUFFER_OVERFLOW;

   // Copy and always null-terminate
   memcpy(sDst, sSrc, count);
   *(sDst + count) = '\0';

   return OK;
}
您可以根据需要更改函数,例如,始终复制尽可能多的字符串而不会溢出。事实上,如果将
\u TRUNCATE
作为
计数传递,VC++实现可以做到这一点





1当然,您仍然需要准确地确定目标缓冲区的大小:如果您提供了一个3字符的缓冲区,但告诉strcpy_s()
它有25个字符的空间,那么您仍然有麻烦。

我一直倾向于:

 memset(dest, 0, LEN);
 strncpy(dest, src, LEN - 1);

到修复后的方法,但这只是一个偏爱的问题。

<代码> StrncPy.()/<代码>不打算用作更安全的<代码> StrcPy()/<代码>,它应该被用来在另一个中间插入一个字符串。

所有这些“安全”字符串处理函数,如
snprintf()
vsnprintf()
都是在以后的标准中添加的修复程序,用于减少缓冲区溢出漏洞等

提到
strncat()
作为编写自己的保险柜
strncpy()
的替代方案:

*dst='\0';
strncat(dst、src、LEN);
编辑

我没有注意到
strncat()
在null终止字符串时超过了LEN个字符,如果长度大于或等于LEN个字符

无论如何,使用
strncat()
而不是任何国产解决方案,如
memcpy(…,strlen(…)
/的意义在于
strncat()
的实现可能在库中进行了目标/平台优化

当然,您需要检查dst是否至少包含nullchar,因此正确使用
strncat()
如下:

if(LEN){
*dst='\0';strncat(dst,src,LEN-1);
}

我还承认,
strncpy()
对于将子字符串复制到另一个字符串中不是很有用,如果src短于n个字符,则目标字符串将被截断。

已经有类似于strlcpy的开源实现可以进行安全复制

在参考文献中有指向源的链接。

最初,文件系统(见目录(5))有目录项,将文件名限制在14字节内;目录中的每个条目由索引节点号的2个字节加上名称的14个字节组成,null填充为14个字符,但不一定以null结尾。我相信,
strncpy()

考虑:

  • 14个字符的文件名未以null结尾
  • 如果名称短于14个字节,则会将其填充为null,直至其全长(14个字节)
这正是通过以下方式可以实现的:

strncpy(inode->d_name, filename, 14);
因此,
strncpy()。这只是为了防止以null结尾的字符串溢出

(请注意,长度为14的空填充不是一个严重的开销-如果缓冲区的长度为4KB,并且您只想安全地将20个字符复制到其中,那么额外的4075个空填充是严重的过度使用,并且如果您反复向长缓冲区添加材料,则很容易导致二次行为。)

使用
strlcpy()
,此处指定:

如果您的libc没有实现,请尝试以下方法:

size_t strlcpy(char* dst, const char* src, size_t bufsize)
{
  size_t srclen =strlen(src);
  size_t result =srclen; /* Result is always the length of the src string */
  if(bufsize>0)
  {
    if(srclen>=bufsize)
       srclen=bufsize-1;
    if(srclen>0)
       memcpy(dst,src,srclen);
    dst[srclen]='\0';
  }
  return result;
}

(我在2004年写的,致力于公共领域。)

这些功能的发展不仅仅是设计上的,所以真的没有“为什么”。 你只需要学会“如何”。不幸的是,linux手册页至少是 这些函数没有常见的用例示例,我注意到了很多 我已经复习过的代码中的误用。我在这里做了一些笔记:

您可以使用

snprintf(buffer, BUFFER_SIZE, "%s", src);
这里有一个单行程序,它最多将
size-1
非空字符从
src
复制到
dest
并添加空终止符:

static inline void cpystr(char *dest, const char *src, size_t size)
{ if(size) while((*dest++ = --size ? *src++ : 0)); }

Strncpy可以更安全地抵御程序的用户的堆栈溢出攻击,它不会保护您免受程序员所做的错误,例如打印非空终止字符串,就像您描述的那样
char my_string[10];
//other code here
printf("%.9s",my_string); //limit the number of chars to be printed to 9
/* copy N "visible" chars, adding a null in the position just beyond them */
#define MSTRNCPY( dst, src, len) ( strncpy( (dst), (src), (len)), (dst)[ (len) ] = '\0')
/* pull up to size - 1 "visible" characters into a fixed size buffer of known size */
#define MFBCPY( dst, src) MSTRNCPY( (dst), (src), sizeof( dst) - 1)