创buildC格式化的string(不打印它们)

我有一个函数接受一个string,即:

void log_out(char *); 

在调用它时,我需要创build一个格式化的string,如下所示:

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

我如何在ANSI C中做到这一点?


只有,因为sprintf()返回一个int,这意味着我必须写至less3个命令,如:

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

任何方法来缩短这个?

使用sprintf 。

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

将格式化的数据写入string如果在printf上使用了格式,则将使用与将要打印的文本相同的string进行组合,但是,不会打印内容,而是将内容作为Cstring存储在str指向的缓冲区中。

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

内容后自动添加一个终止空字符。

在格式参数之后,函数至less需要格式所需的其他参数。

参数:

 str 

指向存储结果Cstring的缓冲区的指针。 缓冲区应该足够大,以包含结果string。

 format 

包含格式string的Cstring,其格式string与printf中的格式相同(请参阅printf以获取详细信息)。

 ... (additional arguments) 

根据格式string的不同,函数可能需要一系列附加参数,每个参数都包含一个用于replace格式string中的格式说明符(或指向存储位置的指针,用于n)的值。 这些参数的数量至less应该和格式说明符中指定的值的数量一样多。 其他参数被函数忽略。

例:

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

这听起来像你希望能够轻松地将使用printf样式格式创build的string传递给您已经拥有的使用简单string的函数。 你可以使用stdarg.h facilities和vsnprintf()来创build一个包装函数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()系列例程的平台,我已经成功地使用了来自Holger Weiss的近乎公开的域snprintf()

如果你有一个POSIX-2008兼容系统(任何现代Linux),你可以使用安全和方便的asprintf()函数:它将malloc()足够的内存给你,你不必担心最大的string大小。 像这样使用它:

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

这是您以安全的方式构buildstring的最小努力。 你在这个问题中给出的sprintf()代码是非常有缺陷的:

  • 指针后面没有分配的内存。 你正在将string写入内存中的一个随机位置!

  • 即使你写了

     char s[42]; 

    你会遇到很大的麻烦,因为你不知道把什么数字放在括号里。

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

  • 如果您尝试在所有情况下使用malloc()snprintf()来产生正确的行为,那么最终的代码大约是我为asprintf()提供的代码的两倍,并且基本上重新编程asprintf()


如果您正在寻找提供可以使用printf()样式参数列表的log_out()的包装, log_out()可以使用将va_list作为参数的变体vasprintf() 。 这是一个非常安全的实现这样一个包装:

 //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"); } } 

如果你有代码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); } 

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

这个解决scheme的好处是,你可以简单地把它称为printf()的变体。 的确,这是printf()上的一个小变种。

如果您没有log_out()代码,请考虑是否可以使用上述types的变体replace它。 是否可以使用相同的名称将取决于您的应用程序框架和当前log_out()函数的最终来源。 如果它与另一个不可或缺的function位于相同的目标文件中,则必须使用新的名称。 如果你不能精确地复制它,你将不得不使用一些类似于其他答案给出的variables来分配适当的内存。

 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()返回的长度是否包含终止空值。

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

C使用<stdarg.h>头文件来规定使用未指定数量的操作数的函数。 你可以定义你的函数为void log_out(const char *fmt, ...); ,并获取函数内的va_list 。 然后你可以分配内存,并用分配的内存,格式和va_list调用vsprintf()

或者,您可以使用它来编写一个类似于sprintf()的函数,该函数将分配内存并返回格式化的string,如上所述生成或多或less的内容。 这将是一个内存泄漏,但如果你只是注销它可能并不重要。

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

  #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,你需要提供足够的缓冲区来打印。