3

对我的一个答案的评论让我有点困惑。当试图计算将两个字符串连接到一个新的内存块需要多少内存时,据说 usingsnprintf优于strlen,如下所示:

size_t length = snprintf(0, 0, "%s%s", str1, str2);
// preferred over:
size_t length = strlen(str1) + strlen(str2);

我可以得到一些背后的原因吗?有什么优势(如果有的话),人们会看到一个结果与另一个不同吗?

4

7 回答 7

5

+1是我说的,我在评论中遗漏了写得又快又粗心的人,所以让我解释一下。我的观点只是,您应该使用使用相同方法的模式来计算最终将用于填充字符串的长度,而不是使用两种可能存在细微差异的不同方法。

例如,如果您有三个字符串而不是两个,并且其中两个或更多重叠,则有可能strlen(str1)+strlen(str2)+strlen(str3)+1超过SIZE_MAX并回绕超过零,从而导致分配不足和截断输出(如果snprintf使用)或极其危险的内存腐败(如果strcpystrcat被使用)。

snprintf will return -1 with errno=EOVERFLOW when the resulting string would be longer than INT_MAX, so you're protected. You do need to check the return value before using it though, and add one for the null terminator.

于 2011-04-10T23:59:03.370 回答
3

如果您只需要确定两个字符串的连接有多大,我看不出有什么特别的理由来选择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);
于 2011-04-10T22:41:59.937 回答
0

一个优点是输入字符串只被扫描一次(在 内部),而不是/解决方案snprintf()的两次。strlenstrcpy

实际上,在重新阅读这个问题以及对您之前的答案的评论时,我看不出sprintf()仅用于计算连接字符串长度的意义何在。如果您实际上是在进行连接,则我的上述段落适用。

于 2011-04-10T22:35:49.960 回答
0

编辑:删除了随机的、错误的废话。我说过吗?

编辑: Matteo 在他下面的评论中是绝对正确的,我是绝对错误的。

从 C99 开始:

2 snprintf 函数等价于 fprintf,除了输出写入数组(由参数 s 指定)而不是流。如果 n 为零,则不写入任何内容,并且 s 可能是空指针。否则,第 n-1 个以外的输出字符将被丢弃,而不是写入数组,并且在实际写入数组的字符的末尾写入一个空字符。如果复制发生在重叠的对象之间,则行为未定义。

返回 3 snprintf 函数返回如果 n 足够大,将写入的字符数,不计算终止的空字符,如果发生编码错误,则返回负值。因此,当且仅当返回值为非负且小于 n 时,以空值结尾的输出已被完全写入。

谢谢你,Matteo,我向 OP 道歉。

这是个好消息,因为它对仅在三周前在这里提出的问题给出了肯定的回答。我无法解释为什么我没有阅读所有答案,这给了我想要的东西。惊人的!

于 2011-04-10T22:38:19.697 回答
0

您需要将 1 添加到 strlen() 示例。请记住,您需要为 nul 终止字节分配空间。

于 2011-04-10T22:43:40.257 回答
0

我在这里可以看到的“优势”是strlen(NULL)可能导致分段错误,而(至少 glibc 的)snprintf()处理NULL参数而不会失败。

因此,使用 glibc-snprintf()您不需要检查其中一个字符串是否为NULL,尽管length可能比需要的稍大,因为(至少在我的系统上)printf("%s", NULL);打印“(null)”而不是什么都没有。


我不建议使用snprintf()而不是strlen()。这并不明显。一个更好的解决方案是一个包装器,strlen()它在参数为 时返回 0 NULL

size_t my_strlen(const char *str)
{
    return str ? strlen(str) : 0;
}
于 2011-04-10T22:59:02.260 回答
0

所以 snprintf( ) 给了我一个字符串的大小。这意味着我可以为那个人 malloc( ) 空间。非常有用。

我想要(但直到现在才找到)snprintf() 的这个函数,因为我格式化了大量的字符串以便稍后输出;但我不想为输出分配静态缓冲区,因为很难预测输出将持续多长时间。所以我最终得到了很多 4096 长的字符数组:-(

但是现在——使用这个(对我而言)新发现的 snprintf() 字符计数函数,我可以 malloc() 输出 bufs在晚上睡觉,两者都可以。

再次感谢并向 OP 和 Matteo 道歉。

于 2011-04-10T23:07:34.600 回答