14

我正在使用 C++。

我想使用 sprintf 编写一个可能很长的格式化字符串(特别是像 _snprintf_s 这样的安全计数版本,但想法是一样的)。近似长度在编译时是未知的,所以我将不得不使用一些动态分配的内存,而不是依赖一个大的静态缓冲区。有什么方法可以确定特定的 sprintf 调用需要多少个字符,这样我就可以始终确定我有足够大的缓冲区?

我的后备方案是我将获取格式字符串的长度,将其加倍,然后尝试。如果有效,那就太好了,如果无效,我将缓冲区的大小加倍,然后再试一次。重复直到合适。不完全是最聪明的解决方案。

看起来 C99 支持将 NULL 传递给 snprintf 以获取长度。我想如果没有别的,我可以创建一个模块来包装该功能,但我对这个想法并不疯狂。

也许 fprintf 到 "/dev/null"/"nul" 可能会起作用?还有其他想法吗?

编辑:或者,有没有办法“分块” sprintf 以便它在写入过程中拾取?如果可能的话,它可以填充缓冲区,处理它,然后从它停止的地方开始重新填充。

4

7 回答 7

24

手册页snprintf说:

   返回值
       成功返回后,这些函数返回的数量
       打印的字符(不包括用于结束的尾随 '\0'
       输出到字符串)。函数 snprintf 和 vsnprintf 不
       写入超过 size 个字节(包括尾随的 '\0')。如果
       由于此限制,输出被截断,然后返回值
       是字符数(不包括结尾的 '\0')
       如果足够的话,这将被写入最终字符串
       空间已经可用。因此,大小或更大的返回值
       表示输出被截断。(另见下文
       注意。)如果遇到输出错误,则为负值
       回来。

这意味着您可以snprintf使用大小为 0 进行调用。不会写入任何内容,返回值会告诉您需要为字符串分配多少空间:

int how_much_space = snprintf(NULL, 0, fmt_string, param0, param1, ...);
于 2009-02-05T07:31:28.263 回答
5

正如其他人所提到的,snprintf()将返回缓冲区中所需的字符数,以防止输出被截断。您可以简单地使用 0 缓冲区长度参数调用它以获得所需的大小,然后使用适当大小的缓冲区。

为了稍微提高效率,您可以使用对于正常情况来说足够大的缓冲区来调用它,并且仅snprintf()在输出被截断时才进行第二次调用。为了确保在这种情况下正确释放缓冲区,我经常使用一个auto_buffer<>为我处理动态内存的对象(并且在堆栈上有默认缓冲区以避免在正常情况下进行堆分配) .

如果您使用的是 Microsoft 编译器,则 MS 有一个非标准_snprintf(),它具有严重的限制,即并不总是以空值终止缓冲区并且不指示缓冲区应该有多大。

为了解决 Microsoft 的不支持问题,我使用了Holger Weiss的一个几乎公共的域snprintf()

当然,如果您的非 MS C 或 C++ 编译器丢失snprintf(),上述链接中的代码也应该可以正常工作。

于 2009-02-05T08:16:10.390 回答
3

我会使用两阶段的方法。通常,大部分输出字符串会低于某个阈值,只有少数会更大。

第 1 阶段,使用大小合理的静态缓冲区,例如 4K。由于snprintf()可以限制写入的字符数,因此不会出现缓冲区溢出。如果缓冲区足够大,您将从中返回snprintf()字符数。

如果您的调用snprintf()返回小于 4K,则使用缓冲区并退出。如前所述,绝大多数电话都应该这样做。

有些不会,那是您进入第 2 阶段的时候。如果对snprintf()4K 缓冲区的调用不适合,那么您至少现在知道需要多大的缓冲区。

使用 分配malloc()一个足够大的缓冲区以容纳它,然后再将其分配snprintf()给该新缓冲区。完成缓冲区后,释放它。

前几天我们在一个系统上工作,我们通过连接和使用snprintf()文件句柄来获得相同的结果。/dev/null 始终保证获取与您提供的数据一样多的数据,因此我们实际上会从中获取大小,然后在必要时分配一个缓冲区。/dev/nullfprintf()

保留并非所有系统都有的实物snprintf()(例如,我知道它_snprintf()在 Microsoft C 中),因此您可能必须找到执行相同工作的函数,或恢复到fprintf /dev/null解决方案。

如果数据可以在大小检查snprintf()和实际snprintf()到缓冲区之间更改(即,注意线程),也要小心。如果大小增加,您将获得缓冲区溢出损坏。

如果您遵循这样的规则,即数据一旦交给一个函数,在交回之前就属于该函数,这不会成为问题。

于 2009-02-05T07:18:39.600 回答
1

值得一提的asprintf是,管理此功能的 GNU 扩展。它接受一个指针作为输出参数,以及一个格式字符串和可变数量的参数,并将包含结果的正确分配缓冲区的地址写回指针。

你可以像这样使用它:

#define _GNU_SOURCE
#include <stdio.h>

int main(int argc, char const *argv[])
{
    char *hi = "hello"; // these could be really long
    char *everyone = "world";
    char *message;
    asprintf(&message, "%s %s", hi, everyone);
    puts(message);
    free(message);
    return 0;
}

希望这对某人有帮助!

于 2014-09-03T23:29:35.757 回答
0

由于您使用的是 C++,因此实际上不需要使用任何版本的 sprintf。最简单的做法是使用 std::ostringstream。

std::ostringstream oss;
oss << a << " " << b << std::endl;

oss.str()返回一个 std::string ,其中包含您写入 oss 的内容。用于oss.str().c_str()获取const char *. 从长远来看,这将更容易处理,并消除内存泄漏或缓冲区溢出。通常,如果您担心 C++ 中的内存问题,那么您并没有充分利用该语言的潜力,您应该重新考虑您的设计。

于 2009-02-05T19:51:17.940 回答
0

我一直在寻找您正在谈论的相同功能,但据我所知,C99 方法这样简单的东西在 C++ 中不可用,因为 C++ 目前不包含 C99 中添加的功能(例如 snprintf) .

您最好的选择可能是使用 stringstream 对象。这比写得很清楚的 sprintf 调用要麻烦一些,但它会起作用。

于 2009-02-05T07:17:41.677 回答
0

看看CodeProject: CString-clone Using Standard C++。它使用您建议的解决方案来扩大缓冲区大小。

// -------------------------------------------------------------------------
    // FUNCTION:  FormatV
    //      void FormatV(PCSTR szFormat, va_list, argList);
    //
// DESCRIPTION: // This function formats the string with sprintf style format-specs. // It makes a general guess at required buffer size and then tries // successively larger buffers until it finds one big enough or a // threshold (MAX_FMT_TRIES) is exceeded. // // PARAMETERS: // szFormat - a PCSTR holding the format of the output // argList - a Microsoft specific va_list for variable argument lists // // RETURN VALUE: // -------------------------------------------------------------------------

void FormatV(const CT* szFormat, va_list argList)
{
#ifdef SS_ANSI

    int nLen    = sslen(szFormat) + STD_BUF_SIZE;
    ssvsprintf(GetBuffer(nLen), nLen-1, szFormat, argList);
    ReleaseBuffer();
#else
    CT* pBuf            = NULL;
    int nChars          = 1;
    int nUsed           = 0;
    size_type nActual   = 0;
    int nTry            = 0;

    do  
    {
        // Grow more than linearly (e.g. 512, 1536, 3072, etc)

        nChars          += ((nTry+1) * FMT_BLOCK_SIZE);
        pBuf            = reinterpret_cast<CT*>(_alloca(sizeof(CT)*nChars));
        nUsed           = ssnprintf(pBuf, nChars-1, szFormat, argList);

        // Ensure proper NULL termination.
        nActual         = nUsed == -1 ? nChars-1 : SSMIN(nUsed, nChars-1);
        pBuf[nActual+1]= '\0';


    } while ( nUsed < 0 && nTry++ < MAX_FMT_TRIES );

    // assign whatever we managed to format

    this->assign(pBuf, nActual);
#endif
}

于 2009-02-05T07:19:25.417 回答