c-什么是strncpy()的最佳替代方案



函数strncpy()并不总是null终止,所以我想知道什么是总是null终止的最佳替代方案?我想要一个函数,如果:

strlen(src) >= n /*n is the number of characters to be copied from source*/

没有必要再添加这样的代码:

buf[sizeof(buf)-1] = 0; 

如果要复制的字符串的长度未知,可以在此处使用snprintf。此函数将格式化输出发送到str。它的作用类似于sprintf(),但不写入str分配的更多字节。如果结果字符串的长度超过n-1个字符,则会忽略其余字符。它还总是包括空终止符,除非缓冲区大小是0

如果你真的不想使用strncpy()strcpy(),这将是一个替代方案。然而,使用strcpy()在字符串末尾手动添加null终止符总是一种简单有效的方法。在C中,在任何处理过的字符串末尾添加一个null终止符是非常正常的。

以下是使用sprintf():的基本示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 1024
int main(void) {
const size_t N = SIZE;
char str[N];
const char *example = "Hello World";
snprintf(str, sizeof(str), "%s", example);
printf("String = %s, Length = %zun", str, strlen(str));
return 0;
}

打印出来的:

String = Hello World, Length = 11

此示例显示snprintf()通过"Hello World"复制到str中,并在末尾添加了终止符。

注意:strlen()仅适用于以null结尾的字符串,如果字符串未以null结尾,则会导致未定义的行为。snprintf()还需要更多的错误检查,可以在手册页上找到。

正如其他人所说,这不是一种有效的方法,但如果你去寻找,它就存在。

如果您想要的行为是strcpy的截断版本,它将源字符串的最长初始前缀复制到已知大小的缓冲区中,则有多种选择:

  • 您可以编写一个定制的函数来完成这项工作:

    char *safe_strcpy(char *dest, size_t size, char *src) {
    if (size > 0) {
    size_t i;
    for (i = 0; i < size - 1 && src[i]; i++) {
    dest[i] = src[i];
    }
    dest[i] = '';
    }
    return dest;
    }
    

    大多数BSD系统都有一个操作相同的函数strlcpy(char *dest, const char *src, size_t n);。其参数的顺序令人困惑,因为n通常是dest数组的大小,但在src参数之后。

  • 您可以使用strncat():

    char *safe_strcpy(char *dest, size_t size, char *src) {
    if (size > 0) {
    *dest = '';
    return strncat(dest, src, size - 1);
    }
    return dest;
    }
    
  • 你可以使用snprintf()sprintf(),但感觉就像用液压机打入钉子:

    snprintf(dest, size, "%s", src);
    

    或者:

    if (size > 0) {
    sprintf(dest, "%.*s", (int)(size - 1), src);
    }
    
  • 您可以使用strlen()memcpy(),但只有当您知道源指针指向以null结尾的字符串时,这才有可能。如果源字符串比目标数组长得多,那么它的效率也低于上述两种解决方案:

    char *safe_strcpy(char *dest, size_t size, char *src) {
    if (size > 0) {
    size_t len = strlen(src);
    if (len >= size)
    len = size - 1;
    memcpy(dest, src, len);
    dest[len] = '';
    }
    return dest;
    }
    

    如果strnlen()在目标系统上可用,则可以避免低效率:

    char *safe_strcpy(char *dest, size_t size, char *src) {
    if (size > 0) {
    size_t len = strnlen(src, size - 1);
    memcpy(dest, src, len);
    dest[len] = '';
    }
    return dest;
    }
    
  • 您可以使用strncpy()并强制null终止。如果目标数组较大,这将是低效的,因为如果源字符串较短,strncpy()还会用空字节填充目标数组的其余部分。该函数的语义非常违背直觉,理解不周,容易出错。即使使用正确,strncpy()的出现也是等待攻击的错误,因为下一个更大胆但不太精明的程序员可能会修改代码并引入它们,试图优化他不完全理解的代码。谨慎行事:避免使用此功能。

这个问题的另一个方面是调用方检测截断的能力。safe_strcpy的上述实现与strcpy一样返回目标指针,因此不向调用方提供任何信息。snprintf()返回一个int,表示如果目标数组足够大,将复制的字符数。在这种情况下,返回值是strlen(src)转换为int,这允许调用方检测截断和其他错误。

以下是另一个更适合从不同部分组成字符串的函数:

size_t strcpy_at(char *dest, size_t size, size_t pos, const char *src) {
size_t len = strlen(src);
if (pos < size) {
size_t chunk = size - pos - 1;
if (chunk > len)
chunk = len;
memcpy(dest + pos, src, chunk);
dest[pos + chunk] = '';
}
return pos + len;
}

此函数可以在没有未定义行为的序列中使用:

void say_hello(const char **names, size_t count) {
char buf[BUFSIZ];
char *p = buf;
size_t size = sizeof buf;
for (;;) {
size_t pos = strcpy_at(p, size, 0, "Hello");
for (size_t i = 0; i < count; i++) {
pos = strcpy_at(p, size, pos, " ");
pos = strcpy_at(p, size, pos, names[i]);
}
pos = strcpy_at(p, size, pos, "!");
if (pos >= size && p == buf) {
// allocate a larger buffer if required
p = malloc(size = pos + 1);
if (p != NULL)
continue;
p = buf;
}
printf("%sn", p);
if (p != buf)
free(p);
break;
}
}

snprintf的等效方法也很有用,通过地址传递pos

size_t snprintf_at(char *s, size_t n, size_t *ppos, const char *format, ...) {
va_list arg;
int ret;
size_t pos = *ppos;
if (pos < n) {
s += pos;
n -= pos;
} else {
s = NULL;
n = 0;
}
va_start(arg, format);
ret = snprintf(s, n, format, arg);
va_end(arg);
if (ret >= 0)
*ppos += ret;
return ret;
}

通过按地址而不是按值传递possnprintf_at可以返回snprintf的返回值,在编码错误的情况下可以是-1

为了说明必须使用strncpy()的替代方案,请考虑Git 2.19(2018年第3季度),它发现滥用系统API函数(如strcat())太容易了;strncpy()。。。这些选定的函数现在在此代码库中被禁止,并将导致编译失败。

该补丁确实列出了几个备选方案,这使得它与这个问题相关。

参见Jeff King(peff)的提交e488b7a、提交cc8fdae、提交1b11b64(2018年7月24日)和提交c8af66a(2018年6月26日)
(由Junio C Hamano合并——gitster——提交e28daf2,2018年8月15日)

banned.h:将strncpy()标记为禁止

strncpy()函数没有strcpy()那么可怕,但由于其有趣的终止语义,仍然很容易被滥用
也就是说,如果它被截断,就会省略NUL终止符,您必须记住自己添加它。即使你使用正确,有时读者也很难在不仔细阅读代码的情况下验证这一点
如果您正在考虑使用它,请考虑:

  • strlcpy()如果您真的只需要一个截断但以NUL结尾的字符串(我们提供了compat版本,所以它总是可用的)
  • xsnprintf()如果您确定要复制的内容应该适合
  • strbufxstrfmt(),如果您需要处理任意长度的堆分配字符串

请注意,compat/regex/regcomp.c中有一个strncpy的实例,这很好(它在复制之前分配一个足够大的字符串)
但即使使用NO_REGEX=1进行编译,这也不会触发禁止列表,因为:

  1. 编译它时,我们不使用git-compatutil.h(相反,我们依赖上游库中的系统includes);以及
  2. 它在"#ifdef DEBUG"块中

由于它不会触发banned.h代码,我们最好将它留给它,以使我们与上游的差异最小。


注意:一年多后,随着Git 2.21(2019年第一季度)的推出,"strncat()"函数本身现在也被禁止使用。

参见Eric Wong(ele828)提交的ace5707(2019年1月2日)
(由Junio C Hamano合并——gitster——提交81bf66b,2019年1月18日)

banned.h:将strncat()标记为禁用

CCD_ 65具有与CCD_ 66相同的二次行为,并且难以读取并且容易出错。虽然它在Git-iself中还没有成为问题,但strncat()发现它进入了cgit的"master",并在我的系统上造成了segfault。


在Git 2.24(2019年第4季度)中,它使用"vsprintf"的显式形式作为自己的禁用版本,而不是"sprintf"。

参见Taylor Blau(ttaylorr)提交的60d198d(2019年8月25日)
(由Junio C Hamano合并——gitster——于2019年9月30日提交37801f0)

作为建议snprintf()的答案的替代方案:(注意:如果n <= 0,则会出现问题)

size_t sz = sizeof buf;
/*n is the number of characters to be copied from source*/
int n = (int) sz - 1;
snprintf(buf, sz, "%s", src);

代码可以使用以下精度

"…s转换要写入的最大字节数……"C11§7.21.6.1 4

sprintf(buf, "%.*s", n, src);

它有一个微妙的优势,即src不需要是字符串,只需要一个字符数组。

另一个字符串工具。

使用strlcpy()函数。

strlcpy()采用目的地缓冲区的完整大小,并在有空间的情况下保证NULL终止。阅读手册页了解更多信息。

strcpy函数总是null终止。当然,您应该包含防止缓冲区溢出的代码,例如:

char buf[50];
if (strlen(src) >= sizeof buf)
{
// do something else...
}
else
strcpy(buf, src);

相关内容

  • 没有找到相关文章

最新更新