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