我是说这句话的人,在我的评论中粗心地快速写下,遗漏了+1
,所以让我解释一下。我的意思只是你应该使用相同的方法来计算最终用于填充字符串的长度,而不是使用两种可能会有微妙差异的不同方法。
例如,如果你有三个字符串而不是两个,并且其中两个或更多重叠,那么strlen(str1)+strlen(str2)+strlen(str3)+1
可能超过SIZE_MAX
并回绕到零,导致输出被截断(如果使用snprintf
)或极其危险的内存损坏(如果使用strcpy
和strcat
)。
snprintf
将在生成的字符串长度大于INT_MAX
时返回-1
,并设置errno=EOVERFLOW
,因此您受到保护。但是,在使用之前需要检查返回值,并添加一个空终止符。
snprintf
,因为两个strlen
调用可以完成确定两个字符串总长度的最小操作。snprintf
几乎肯定会更慢,因为它除了遍历两个字符串计算字符数之外,还必须检查参数并解析格式字符串。snprintf
可能是一个明智的选择,例如:/* static buffer "big enough" for most cases */
char buffer[256];
/* pointer used in the part where work on the string is actually done */
char * outputStr=buffer;
/* try to concatenate, get the length of the resulting string */
int length = snprintf(buffer, sizeof(buffer), "%s%s", str1, str2);
if(length<0)
{
/* error, panic and death */
}
else if(length>sizeof(buffer)-1)
{
/* buffer wasn't enough, allocate dynamically */
outputStr=malloc(length+1);
if(outputStr==NULL)
{
/* allocation error, death and panic */
}
if(snprintf(outputStr, length, "%s%s", str1, str2)<0)
{
/* error, the world is doomed */
}
}
/* here do whatever you want with outputStr */
if(outputStr!=buffer)
free(outputStr);
我认为这里的“优点”是,strlen(NULL)
可能会导致段错误,而(至少glibc的)snprintf()
可以在不失败的情况下处理 NULL
参数。
因此,使用glibc-snprintf()
时,您不需要检查字符串之一是否为NULL
,尽管length
可能会比实际需要稍微大一些,因为(至少在我的系统上)printf("%s", NULL);
会打印“(null)”而非空字符串。
然而,我不建议使用snprintf()
代替strlen()
。这不够明显。一个更好的解决方案是编写一个strlen()
的包装器,在参数是NULL
时返回0:
size_t my_strlen(const char *str)
{
return str ? strlen(str) : 0;
}
snprintf
不接受 %s
格式说明符参数的 NULL
值。 - R.. GitHub STOP HELPING ICEsnprintf()
非常乐意接受 %s
格式说明符的 NULL
参数。 - Philipmy_strlen
包装器将返回0用于“NULL”,而glibc的snprintf
将输出6个字节用于“NULL”。这可能会导致字符串后面的部分被截断,或者如果有人愚蠢到只使用sprintf
并假设正确的长度已被计算,则会导致缓冲区溢出。 - R.. GitHub STOP HELPING ICE一个优点是输入字符串只被扫描了一次(在 snprintf()
中),而不是两次用于 strlen
/strcpy
解决方案。
实际上,在重新阅读这个问题和你之前回答的评论后,我没有看出使用 sprintf()
来仅仅计算连接字符串长度的意义所在。如果你实际上正在进行连接操作,那么我的上面一段话就适用了。
man snprintf
文档:"当以 size=0 调用 snprintf()
时... C99 允许在这种情况下 str 参数为 NULL,并像往常一样返回已经写入的字符数,假设输出字符串足够大。" - Greg Hewgill编辑:已删除随机、错误的无意义内容。我说过那个吗?
编辑:在下面评论中,Matteo是绝对正确的,而我是绝对错误的。
来自C99:
2 snprintf函数等同于fprintf,不同之处在于输出被写入一个数组(由参数s指定),而不是流中。如果n为零,则不写入任何内容,s可以是空指针。否则,超出第n-1个字符的输出将被丢弃,而不是写入数组,并在实际写入数组的字符末尾写入一个空字符。如果在重叠对象之间进行复制,则行为未定义。
返回值:
3 snprintf函数返回将被写入的字符数,假设n足够大,不包括终止的空字符,或者如果发生编码错误则返回负值。因此,仅当返回值为非负且小于n时,才完全写入以空字符结尾的输出。
谢谢你,Matteo,我向OP道歉。
这是个好消息,因为它给了我一个我三周前在这里提出的问题的积极答案。我无法解释为什么我没有阅读所有的回答,而那些回答给了我想要的东西。太棒了!
snprintf
手册中可以看到:“关于snprintf()
的返回值,SUSv2和C99相互矛盾:当size=0
时,SUSv2规定返回值为小于1的未指定值,而C99允许在这种情况下str
为空,并且(像往常一样)返回值是输出字符串足够大时将要写入的字符数。” 因此,在两种情况下都不会出现核心转储,并且只要您的编译器符合C99标准,该调用就是定义良好的。 - Matteo Italian
为零,则不会写入任何内容,且s
可以是空指针。”(以及¶3)“snprintf
函数返回将被写入的字符数,假设n
足够大,但不包括终止的空字符。” - Matteo Italiasnprintf
表达式中加 1 也是必要的,因为它不包含在返回值中。出于简单起见,我只是从两个中省略了它。 - user142162所以snprintf()给了我一个字符串的大小,这意味着我可以为它分配空间。非常有用。
我一直想要(但直到现在才找到)snprintf()的这个函数,因为我会为输出格式化大量的字符串;但我不想为输出分配静态缓冲区,因为很难预测输出的长度。所以我最终得到了很多长度为4096的字符数组 :-(
但现在 - 使用这个新发现的(对我来说)snprintf()计数函数,我可以malloc()输出缓冲区并且晚上也能睡得安稳。
再次感谢OP和Matteo。