strncpy()
函数并不总是以空字符结尾,因此我想知道什么是始终以空字符结尾的最佳替代方法?我需要一个函数,如果:
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
,除非缓冲区大小为0
。strncpy()
或strcpy()
,这将是一种替代方法。然而,使用strcpy()
手动添加一个空终止符总是一个简单、高效的方法。在C中,在任何处理的字符串末尾添加一个空终止符是很正常的。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 = %zu\n", str, strlen(str));
return 0;
}
哪个会打印出:
String = Hello World, Length = 11
这个例子展示了snprintf()
将"Hello World"
复制到str
中,并在结尾添加了一个\0
终止符。
注意:strlen()
仅适用于以空字符结尾的字符串,如果字符串没有以空字符结尾,则会导致未定义的行为。snprintf()
还需要更多的错误检查,可以在man页面上找到相关信息。
正如其他人所说,这不是一种高效的方法,但如果你有需要的话,它也能解决问题。
snprintf(dst, DST_SIZE, src);
。 - user694733snprintf()
是一个有效的选择。从他的问题来看,效率似乎不是他最高的优先级。 - RoadRunner如果您想要的行为是 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] = '\0';
}
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 = '\0';
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()
函数。但是,如果源字符串比目标数组长得多,这种方法也不如上面两种解决方案有效率:
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] = '\0';
}
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] = '\0';
}
return dest;
}
您可以使用 strncpy()
并强制添加空终止字符。但如果目标数组很大,这样做效率不高,因为strncpy()
在源字符串较短时还会用空字节填充目标数组的其余部分。此函数的语义非常反直觉、难以理解且容易出错。即使正确使用时,strncpy()
的使用也会导致潜在的错误,因为下一个程序员可能会更加大胆但不够精通,试图对他不完全理解的代码进行优化并引入错误。安全起见:请避免使用此函数。
另一个问题是调用者能否检测到截断。上述实现中的 safe_strcpy
返回目标指针,就像 strcpy
一样,因此不向调用者提供任何信息。snprintf()
返回一个表示如果目标数组足够大将要复制的字符数的整数值,在本例中,返回值为 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] = '\0';
}
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("%s\n", 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;
}
通过地址传递 pos
而不是值,使得 snprintf_at
可以返回 snprintf
的返回值,如果出现编码错误,则可能为 -1
。
strncpy()
的替代方案的示例,考虑Git 2.19(2018年第三季度),发现滥用系统API函数如strcat(); strncpy()
; ...这些选择的函数现在在此代码库中被禁止,并将导致编译失败。该补丁列出了几个替代方案,使其与此问题相关。
请查看由Jeff King (peff
)提交的提交 e488b7a, 提交 cc8fdae, 提交 1b11b64(2018年7月24日)和提交 c8af66a(2018年7月26日)。
(由Junio C Hamano -- gitster
--在提交 e28daf2中合并,于2018年8月15日)
banned.h
:将strncpy()
标记为禁用strncpy()
函数比strcpy()
好一些,但由于其有趣的终止语义,仍然很容易被误用。
即,如果它截断了字符串,则会省略NUL终止符,您必须自己记得添加它。即使您正确使用它,有时读者也很难在不查找代码的情况下验证它是否正确。
如果您考虑使用它,请考虑使用以下替代方法:
strlcpy()
,如果您只需要一个被截断但带NUL终止符的字符串(我们提供了兼容版本,因此始终可用)xsnprintf()
,如果您确定要复制的内容可以放下strbuf
或xstrfmt()
,如果您需要处理任意长度的堆分配字符串。请注意,在compat/regex/regcomp.c
中有一个strncpy
实例,这是可以接受的(在复制之前它会分配足够大的字符串)。
但是,即使使用NO_REGEX=1
编译,这也不会触发禁用列表,因为:
git-compat-util.h
(而是依赖于上游库的系统包含文件);以及#ifdef DEBUG
”块中由于它不会触发banned.h
代码,因此最好保留它,以使我们与上游的差异最小化。
strncat()
”函数本身也成为被禁止的函数之一。ele828
)于2019年1月2日提交)。
(由Junio C Hamano -- gitster
--合并至提交81bf66b,2019年1月18日)
banned.h
: 将strncat()
标记为被禁用的函数
strncat()
和strcat()
一样具有二次行为,而且难以阅读和容易出错。尽管它在 Git 中还没有成为问题,但strncat()
已经出现在 'cgit
' 的 'master
' 分支中,并在我的系统上导致了段错误。
在 Git 2.24 (2019年第四季度) 中,它使用显式形式的 'vsprintf
' 作为其自身被禁用的版本,而不是 'sprintf
'。
请参见 提交记录60d198d (2019年8月25日),作者为 Taylor Blau (ttaylorr
)。
(由 Junio C Hamano -- gitster
-- 在 提交记录37801f0 中合并,于2019年9月30日)
作为对建议使用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
不是标准函数,我个人更喜欢使用标准函数。 - cadanilukstrcpy_s
函数,它是C11标准的一部分,但编译器不一定支持它。尽管如此,它仍比strlcpy
更标准。而且没有真正的理由使用它们中的任何一个... - Lundinstrncpy_s
。但是,strncpy_s
容易出现偏移错误。显然,必须指定目标缓冲区大小减一。strlcpy
更加自然。 - Svenstrcpy
函数总是以空字符结尾。当然,您应该包含代码来防止缓冲区溢出,例如:
char buf[50];
if (strlen(src) >= sizeof buf)
{
// do something else...
}
else
strcpy(buf, src);
strncpy_s
函数的文档,该函数属于 C 语言中处理字符串的一部分。 - Yuriy Ivaskevychsnprintf
始终会添加字符串结束符。 - M.Mstrncpy
的第三个参数是输出缓冲区的大小,而不是要复制的字符数。 - M.Mstrlen(src) < n
的情况(如果是,那么在这种情况下您希望发生什么)。 - M.M