创建C格式的字符串(不打印它们)

时间:2009-04-29 21:10:04

标签: c string

我有一个接受字符串的函数,即:

void log_out(char *);

在调用它时,我需要动态创建一个格式化的字符串,如:

int i = 1;
log_out("some text %d", i);

我如何在ANSI C中执行此操作?


仅,因为sprintf()返回一个int,这意味着我必须编写至少3个命令,如:

char *s;
sprintf(s, "%d\t%d", ix, iy);
log_out(s);

有什么方法可以缩短这个吗?

7 个答案:

答案 0 :(得分:74)

使用sprintf

int sprintf ( char * str, const char * format, ... );
  

将格式化数据写入字符串组成具有相同文本的字符串   如果在printf上使用了格式,则会打印出来,而不是   在打印时,内容作为C字符串存储在缓冲区中   str。指出。

     

缓冲区的大小应足够大以包含整个缓冲区   结果字符串(请参阅snprintf获取更安全的版本)。

     

终止空字符会自动附加到   内容。

     

在format参数之后,该函数至少需要这么多   格式化所需的其他参数。

参数:

str
  

指向存储结果C字符串的缓冲区的指针。缓冲区   应该足够大以包含结果字符串。

format
  

包含格式字符串的C字符串   printf格式的规范(详见printf)。

... (additional arguments)
  

根据格式字符串,函数可能需要一系列   其他参数,每个参数包含一个用于替换a的值   格式字符串中的格式说明符(或指向存储的指针)   位置,对于n)。应该至少有这么多的论点   作为格式说明符中指定的值的数量。额外   函数忽略了参数。

实施例

// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello" "world");

答案 1 :(得分:11)

听起来我希望能够轻松地将使用printf样式格式创建的字符串传递给您已经拥有的带有简单字符串的函数。您可以使用stdarg.h工具和vsnprintf()创建包装函数(根据您的编译器/平台,这可能不是很容易获得):

#include <stdarg.h>
#include <stdio.h>

// a function that accepts a string:

void foo( char* s);

// You'd like to call a function that takes a format string 
//  and then calls foo():

void foofmt( char* fmt, ...)
{
    char buf[100];     // this should really be sized appropriately
                       // possibly in response to a call to vsnprintf()
    va_list vl;
    va_start(vl, fmt);

    vsnprintf( buf, sizeof( buf), fmt, vl);

    va_end( vl);

    foo( buf);
}



int main()
{
    int val = 42;

    foofmt( "Some value: %d\n", val);
    return 0;
}

对于没有提供snprintf()例程系列的良好实现(或任何实现)的平台,我已成功使用a nearly public domain snprintf() from Holger Weiss

答案 2 :(得分:11)

如果您有符合POSIX-2008标准的系统(任何现代Linux),您可以使用安全且方便的asprintf()功能:malloc()将为您提供足够的内存,您不需要担心最大字符串大小。像这样使用它:

char* string;
if(0 > asprintf(&string, "Formatting a number: %d\n", 42)) return error;
log_out(string);
free(string);

这是您以安全的方式构建字符串所需的最小努力。您在问题中提供的sprintf()代码存在严重缺陷:

  • 指针后面没有分配的内存。您正在将字符串写入内存中的随机位置!

  • 即使您已经写过

    char s[42];
    

    你会陷入深深的麻烦,因为你无法知道括号中的数字。

  • 即使您使用了“安全”变体snprintf(),您仍然会遇到字符串被截断的危险。写入日志文件时,这是一个相对较小的问题,但它有可能准确地切断本来有用的信息。此外,它会切断尾随的结束字符,将下一个日志行粘贴到未成功写入行的末尾。

  • 如果您尝试使用malloc()snprintf()的组合在所有情况下都能产生正确的行为,那么最终的代码大约是我{{1}的代码的两倍基本上重新编程asprintf()

  • 的功能

如果您正在考虑提供asprintf()的包装器,可以自行选择log_out()样式参数列表,则可以使用printf()作为vasprintf()的变体va_list一个论点。这是一个非常安全的包装器实现:

//Tell gcc that we are defining a printf-style function so that it can do type checking.
//Obviously, this should go into a header.
void log_out_wrapper(const char *format, ...) __attribute__ ((format (printf, 1, 2)));

void log_out_wrapper(const char *format, ...) {
    char* string;
    va_list args;

    va_start(args, format);
    if(0 > vasprintf(&string, format, args)) string = NULL;    //this is for logging, so failed allocation is not fatal
    va_end(args);

    if(string) {
        log_out(string);
        free(string);
    } else {
        log_out("Error while logging a message: Memory allocation failed.\n");
    }
}

答案 3 :(得分:3)

如果您有log_out()的代码,请重写它。最有可能的是,你可以这样做:

static FILE *logfp = ...;

void log_out(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    vfprintf(logfp, fmt, args);
    va_end(args);
}

如果需要额外的日志记录信息,可以在显示的消息之前或之后打印。这节省了内存分配和可疑的缓冲区大小等等。您可能需要将logfp初始化为零(空指针)并检查它是否为null并根据需要打开日志文件 - 但现有log_out()中的代码应该正在处理它。

此解决方案的优势在于您可以简单地将其称为printf()的变体;实际上,它是printf()的一个小变种。

如果您没有log_out()的代码,请考虑是否可以使用上述变体替换它。是否可以使用相同的名称取决于您的应用程序框架和当前log_out()函数的最终来源。如果它与另一个必不可少的函数位于同一个目标文件中,则必须使用新名称。如果你无法弄清楚如何完全复制它,你将不得不使用一些变体,如在其他答案中给出的那些分配适当数量的内存。

void log_out_wrapper(const char *fmt, ...)
{
    va_list args;
    size_t  len;
    char   *space;

    va_start(args, fmt);
    len = vsnprintf(0, 0, fmt, args);
    va_end(args);
    if ((space = malloc(len + 1)) != 0)
    {
         va_start(args, fmt);
         vsnprintf(space, len+1, fmt, args);
         va_end(args);
         log_out(space);
         free(space);
    }
    /* else - what to do if memory allocation fails? */
}

显然,您现在调用log_out_wrapper()而不是log_out() - 但内存分配等等只执行一次。我保留通过一个不必要的字节过度分配空间的权利 - 我没有仔细检查vsnprintf()返回的长度是否包括终止空值。

答案 4 :(得分:2)

不要使用sprintf 它会溢出你的String-Buffer并使你的程序崩溃 始终使用snprintf

答案 5 :(得分:0)

我没有这样做,所以我只是指出正确的答案。

C使用<stdarg.h>标头为使用未指定数量的操作数的函数提供了条件。您可以将函数定义为void log_out(const char *fmt, ...);,并在函数内部获取va_list。然后,您可以分配内存并使用分配的内存,格式和vsprintf()调用va_list

或者,您可以使用它来编写类似于sprintf()的函数,该函数将分配内存并返回格式化字符串,如上所述或多或少地生成它。这将是一个内存泄漏,但如果你刚刚退出它可能无关紧要。

答案 6 :(得分:-2)

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html给出了以下示例来打印到stderr。您可以修改它以使用您的日志功能:

 #include <stdio.h>
 #include <stdarg.h>

 void
 eprintf (const char *template, ...)
 {
   va_list ap;
   extern char *program_invocation_short_name;

   fprintf (stderr, "%s: ", program_invocation_short_name);
   va_start (ap, template);
   vfprintf (stderr, template, ap);
   va_end (ap);
 }

您需要使用vsprintf而不是vfprintf,您需要提供足够的缓冲区来打印。