有没有办法确定sprintf将写入多少个字符?

时间:2009-02-05 07:06:37

标签: c++ string printf

我在使用C ++。

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

我的后备是我只需要获取格式字符串的长度,加倍,然后尝试。如果它工作,很好,如果不是,我只会加倍缓冲区的大小,然后再试一次。重复直到它适合。不完全是最聪明的解决方案。

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

或许“/ dev / null”/“nul”的fprintf可能会起作用吗?还有其他想法吗?

编辑:或者,是否有任何方法可以“冲破”sprintf,以便它在中间写入?如果可能的话,它可以填充缓冲区,处理它,然后从它停止的地方开始重新填充。

7 个答案:

答案 0 :(得分:22)

snprintf的手册页说:

   Return value
       Upon  successful  return,  these  functions return the number of
       characters printed (not including the trailing '\0' used to  end
       output to strings).  The functions snprintf and vsnprintf do not
       write more than size bytes (including the  trailing  '\0').   If
       the output was truncated due to this limit then the return value
       is the number of characters (not including  the  trailing  '\0')
       which  would  have  been  written  to the final string if enough
       space had been available. Thus, a return value of size  or  more
       means  that  the  output  was  truncated.  (See also below under
       NOTES.)  If an output error is encountered, a negative value  is
       returned.

这意味着您可以调用大小为0的snprintf。没有任何内容可写,返回值将告诉您需要为字符串分配多少空间:

int how_much_space = snprintf(NULL, 0, fmt_string, param0, param1, ...);

答案 1 :(得分:5)

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

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

如果您使用的是Microsoft编译器,则MS具有非标准_snprintf(),其严重限制是不会始终为null终止缓冲区而不指示缓冲区应该有多大。

要解决Microsoft的不支持问题,我使用a nearly public domain snprintf() from Holger Weiss

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

答案 2 :(得分:2)

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

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

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

有些人不会这样,当你进入第2阶段时。如果对snprintf()的调用不适合4K缓冲区,你至少现在知道你需要多大的缓冲区。

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

我们在snprintf()之前的几天内处理了一个系统,我们通过将文件句柄连接到/dev/null并使用fprintf()来实现相同的结果。 / dev / null始终保证获取尽可能多的数据,因此我们实际上从中获取大小,然后在必要时分配缓冲区。

保持并非所有系统都具有snprintf()(例如,我理解它是Microsoft C中的_snprintf()),因此您可能必须找到执行相同工作的功能,或者还原为fprintf /dev/null解决方案。

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

如果遵循以下规则:数据一旦交给某个函数,就属于该函数专属直到交还,这不会有问题。

答案 3 :(得分: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;
}

希望这有助于某人!

答案 4 :(得分:0)

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

您最好的选择可能是使用stringstream对象。它比一个清晰的sprintf调用更麻烦,但它会起作用。

答案 5 :(得分: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
}

答案 6 :(得分: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 ++中的内存问题,那么您并没有充分利用该语言,您应该重新考虑您的设计。