我有一个接受字符串的函数,即:
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);
有什么办法可以缩短?
我有一个接受字符串的函数,即:
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);
有什么办法可以缩短?
Answers:
使用sprintf。
int sprintf ( char * str, const char * format, ... );
将格式化的数据写入字符串如果在printf上使用format,则使用与要打印的文本组成的字符串组成一个字符串,但是内容将作为C字符串存储在由str指向的缓冲区中,而不是被打印。
缓冲区的大小应足够大以容纳整个结果字符串(有关安全版本,请参见snprintf)。
内容后面会自动附加一个终止的空字符。
在format参数之后,该函数至少需要与format一样多的附加参数。
str
指向存储结果C字符串的缓冲区的指针。缓冲区应足够大以包含结果字符串。
format
包含格式字符串的C字符串,其格式与printf中的格式相同(有关详细信息,请参见printf)。
... (additional arguments)
根据格式字符串的不同,该函数可能需要一系列附加参数,每个参数都包含一个用于替换格式字符串中的格式说明符的值(或n指向存储位置的指针)。这些参数至少应与格式说明符中指定的值数量一样多。该函数将忽略其他参数。
// Allocates storage
char *hello_world = (char*)malloc(13 * sizeof(char));
// Prints "Hello world!" on hello_world
sprintf(hello_world, "%s %s!", "Hello", "world");
如果您拥有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];
您将陷入困境,因为您不知道将哪个数字放在括号中。
即使您使用了“ safe”变体snprintf()
,您仍然会面临字符串被截断的危险。在写入日志文件时,这是一个相对较小的问题,但是它有可能精确地切断有用的信息。而且,它将切断结尾的结尾字符,将下一条日志行粘贴到未成功写入的行的末尾。
如果尝试在所有情况下使用的组合malloc()
并snprintf()
产生正确的行为,那么最终得到的代码大约是我给出的两倍asprintf()
,并且基本上对的功能进行了重新编程asprintf()
。
如果您正在寻找一种包装,log_out()
它printf()
本身可以使用样式参数列表,则可以使用vasprintf()
将a 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");
}
}
asprintf()
它既不是标准C 2011的一部分,也不是POSIX的一部分,甚至不是POSIX 2008或2013的一部分。它不是TR 27431-2的一部分:请参阅是否使用TR 24731的“安全”功能?
在我看来,您希望能够轻松地将使用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()
例程系列的平台,我已经成功地使用了snprintf()
Holger Weiss 的几乎公共领域。
如果您有的代码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
为零(空指针),并检查它是否为空,并适当地打开日志文件-但是现有代码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()
包含终止null。
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,在这里需要提供足够的缓冲区以供打印。