如何计算 sprintf 将生成的输出长度?

How to calculate the length of output that sprintf will generate?

目标:将数据序列化为JSON。

问题:我无法事先知道整数有多少个字符。

我认为一个很好的方法是使用 sprintf()

size_t length = sprintf(no_buff, "{data:%d}",12312);
char *buff = malloc(length);
snprintf(buff, length, "{data:%d}",12312);
//buff is passed on ...

当然我可以使用像char a[256]这样的堆栈变量来代替no_buff

问题:但是在C中有没有像unix一样的一次性写入的实用程序/dev/null? 像这样:

#define FORGET_ABOUT_THIS ...
size_t length = sprintf(FORGET_ABOUT_THIS, "{data:%d}",12312);

p.s。我知道我也可以通过日志获取整数的长度,但这种方式似乎更好。

您可以致电int len = snprintf(NULL, 0, "{data:%d}", 12312)来测试您需要多少space。

snprintf 将最多打印 size 个字符,其中 size 是第二个参数,return 打印整个内容需要多少个字符,不包括终止 '[=15=]'。因为你传入 0,它实际上不会写出任何东西(因此将避免通过尝试取消引用 NULL 会发生的任何空指针异常),但它仍然会 return 的长度需要适合整个输出,您可以使用它来分配缓冲区。

此时您可以分配并打印到您的缓冲区,记得为尾随 '[=15=]':

添加一个缓冲区
char *buf = malloc(len + 1);
snprintf(buf, len + 1, "{data:%d}", 12312);

为了得到你可以写的长度:

int length = snprintf(NULL, 0, "{data:%d}", 12312);

注意 return 类型是 int。如果出现某种错误,它可能 return -1。确保您的输入数据不包含可能导致总长度超过 INT_MAX !

的长字符串

因为 C 是简单的语言,所以没有 "disposable buffers" 这样的东西——所有的内存管理都在程序员的肩上(有针对这些的 GNU C 编译器扩展,但它们不是标准的)。

cant know beforehand how many chars long the integer is.

您的问题有更简单的解决方案。 snprintf知道了!

在 C99 兼容平台上调用 snprintf 并将 NULL 作为第一个参数:

ssize_t bufsz = snprintf(NULL, 0, "{data:%d}",12312);
char* buf = malloc(bufsz + 1);
snprintf(buf, bufsz + 1, "{data:%d}",12312);

...

free(buf);

在较旧的 Visual Studio 版本(具有非 C99 兼容的 CRT)中,使用 _scprintf 而不是 snprintf(NULL, ...) 调用。

严格来说,这并不是您问题的答案,但您可能会发现它对您有所帮助。它不可移植,但如果你在 glibc 上 运行 这个,你可以简单地使用 asprintf() 来代替,它将为你分配内存。

如果您检查性能,您会 运行在没有输出缓冲区的情况下使用 snprintf 将花费与完整调用大致相同的时间。

所以我建议您使用较小的缓冲区以防万一,并且只有在返回的大小超过缓冲区大小时才再次调用它。

这使用 C++ 的 std::string,但我想您可以根据需要调整它。

std::string format(const char* format, ...) {
    va_list args;
    va_start(args, format);
    char smallBuffer[1024];
    int size = vsnprintf(smallBuffer, sizeof smallBuffer, format, args);
    va_end(args);

    if (size < sizeof smallBuffer) 
        return std::string(smallBuffer);

    char buffer[size  + 1]; /* maybe malloc if it's too big */

    va_start(args, format);
    vsnprintf(buffer, sizeof buffer, format, args);
    va_end(args);
    return std::string(buffer);
}

对于 1k 以下的字符串,与较长的字符串相比,此代码 运行 快 2 倍。

Printf支持%n格式参数,即“将%n在输出字符串中的位置写入x参数指向的int值”,所以:

int x;snprintf(NULL, 0, "Message: %s%n", "Error!",&x);

应该可以!

调用 snprintf(nullptr, 0, ...) 会 return 大小,但它有性能损失,因为它会调用 IO_str_overflow 并且速度很慢。

如果您确实关心性能,您可以预先分配一个虚拟缓冲区并将其指针和大小传递给::snprintf。它将比 nullptr 版本快几倍。

template<typename ...Args>
size_t get_len(const char* format, Args ...args) {
  static char dummy[4096]; // you can change the default size
  return ::snprintf(dummy, 4096, format, args...) + 1; // +1 for [=10=]
}