我经常希望有一种简单的方法来使用 sprintf/snprintf 构造字符串,而无需定义本地数组的麻烦,如下所示:
char str[256];
snprintf(str, sizeof(str), format, ...);
use_string(str);
我突然想到可以使用复合文字来做到这一点:
static inline char* snprintf_assert(char* str, int max_size, const char* format, ...) {
va_list arg;
va_start(arg, format);
int count = vsnprintf(str, max_size, format, arg);
va_end(arg);
assert(count >= 0 && count < max_size);
return str;
}
// Helper sprintf that allocates a buffer local to the block.
// It's using the fact that (char[N]){} will allocate a l-value
// with lifetime tied to the local block.
#define local_sprintf(N, ...) snprintf_assert((char[N]){}, N, __VA_ARGS__)
用法:
use_string(local_sprintf(256, format, ...));
我相信这是明确定义的,因为复合文字数组的生命周期将与封闭 block 相关联。有什么理由可以避免这种情况吗?
最佳答案
与在调用 snprintf_assert
之前简单定义本地数组相比,此技术存在一些缺点:
复合文字的可移植性较差:定义固定大小的数组
N
(例如:256
)可移植到 C++ 和不支持的 C 实现支持 VLA 或复合文字,在 block 的开头定义数组当然是完全可移植的。这种技术效率较低:复合文字
(char[N]){}
,您可以将其编写为(char[N]){0}
为了向后移植到 C99,定义并初始化临时未命名数组,因此您会产生N
字节的额外memset()
到0
。这可能不是一个很大的开销,除非您使N
很大以容纳潜在的大型构造字符串。可读性较差:这种技术并不常见,代码的临时读者会思考
local_sprintf
返回的指针的生命周期,尽管有明确的名称。
相反,以下是一些明显的优势:
多功能性:可在任何表达式中使用的简单函数调用语法。
简单:无需命名临时字符数组。
可以通过本地定义的具有相同作用域和生命周期的指针多次使用。
char *fullname = local_sprintf(256, "%s %s", first, last); register_user(fullname); output_user(fullname);
但是对于这个用例来说,相对于经典代码的优势似乎微不足道:
char fullname[156]; snprintf(fullname, sizeof fullname, "%s %s", first, last); register_user(fullname); output_user(fullname);
遗憾的是,数组长度仍然需要估计。当然,实际的数组大小可以通过对宏中的 snprintf
的额外调用来计算和传递,并通过另一个调用将其传递给 snprintf_assert,但应避免对宏参数进行多次求值并求值 snprintf
3 次至少可以说似乎不太优雅。
关于c - 使用复合文字数组作为临时字符串缓冲区是否合理?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/77359768/