计算字符串长度的不同方法

对我的一个答案的评论让我有点困惑。当试图计算将两个字符串连接到一个新的内存块需要多少内存时,据说使用
snprintf
优于
strlen
,如下所示:
size_t length = snprintf(0, 0, "%s%s", str1, str2);
// preferred over:
size_t length = strlen(str1) + strlen(str2);
我可以在这背后得到一些推理吗?有什么优势,如果有的话,有人会看到一个结果与另一个结果不同吗?     
已邀请:
我是那个说出来的人,我在评论中遗漏了
+1
,这篇评论是快速而粗心地写的,所以让我解释一下。我的观点仅仅是你应该使用相同方法的模式来计算最终用于填充字符串的长度,而不是使用两种可能在细微方面存在差异的不同方法。 例如,如果你有三个字符串而不是两个字符串,并且它们中的两个或多个重叠,则
strlen(str1)+strlen(str2)+strlen(str3)+1
可能超过
SIZE_MAX
并且包裹过零,导致输出分配不足和截断(如果使用
snprintf
)或非常危险的内存损坏(如果使用
strcpy
strcat
)。 当结果字符串长于
INT_MAX
时,
snprintf
将返回
-1
,so11ѭ,因此您受到保护。您确实需要在使用它之前检查返回值,并为null终止符添加一个。     
如果你只需要确定两个字符串串联的大小,我没有看到任何特别的理由偏爱
snprintf
,因为确定两个字符串的总长度的最小操作是两个
strlen
调用所做的。
snprintf
几乎肯定会慢一些,因为它必须检查参数并解析格式字符串,而不仅仅是走两个字符串来计算字符。 ...但是...如果你想要连接两个字符串,并且有一个静态的,不是太大的缓冲区来处理正常情况,那么使用ѭ0可能是一个聪明的举动,但你可以回退到在大字符串的情况下动态分配缓冲区,例如:
/* 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);
    
一个优点是输入字符串只扫描一次(在
snprintf()
内)而不是两次用于
strlen
/
strcpy
解决方案。 实际上,在重读这个问题以及对你之前的答案的评论时,我看不出使用
sprintf()
只是为了计算连接的字符串长度有什么意义。如果您实际上正在进行连接,我的上一段适用。     
您需要在strlen()示例中添加1。记住你需要为nul终止字节分配空间。     
所以snprintf()给了我一个字符串的大小。这意味着我可以为那个人提供malloc()空间。非常有用。 我希望(但直到现在才发现)snprintf()的这个函数,因为我格式化了大量的字符串以便稍后输出;但我不想为输出分配静态buf,因为很难预测输出会有多长。所以我最终得到了很多4096长的char数组:-( 但是现在 - 使用这个新发现的(对我来说)snprintf()字符计数功能,我可以在晚上输出bufs和睡眠,两者都是。 再次感谢并向OP和Matteo道歉。     
编辑:随机,错误的废话删除。我说的是吗? 编辑:Matteo在下面的评论中是绝对正确的,我绝对错了。 来自C99: 2除了输出写入之外,snprintf函数等效于fprintf 数组(由参数s指定)而不是流。如果n为零,则不写任何内容, 和s可以是空指针。否则,超出n-1的输出字符是 丢弃而不是写入数组,并在末尾写入空字符 实际写入数组的字符。如果在对象之间进行复制 重叠,行为是不确定的。 返回 3 snprintf函数返回已写入的字符数 已经足够大,不计算终止空字符,或者是否定义 如果发生编码错误,则为value。因此,空终止输出已经 当且仅当返回值为非负且小于n时才完全写入。 谢谢你,Matteo,我向OP道歉。 这是一个好消息,因为它给我在三周前问过的一个问题给出了积极的答案。我无法解释为什么我没有阅读所有答案,这给了我想要的东西。真棒!     
我在这里可以看到的“优势”是
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;
}
    

要回复问题请先登录注册