我有一个接受字符串的函数,即:
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);
使用sprintf函数。(这并不安全,但是OP要求一个ANSI C的答案。请查看评论以获取更安全的版本。)
int sprintf ( char * str, const char * format, ... );
将格式化的数据写入字符串。该函数返回一个与使用printf打印相同文本的字符串,但不是直接打印输出,而是存储在由str指向的字符数组缓冲区中。
缓冲区的大小应足够大,以容纳整个结果字符串(请参见snprintf获取更安全的版本)。
内容后会自动添加一个终止空字符。
在格式参数之后,该函数期望提供至少所需数量的额外参数用于格式化。
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()
函数:它会为您分配足够的内存,您无需担心字符串大小的最大值。使用以下方式:
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()
的组合来在所有情况下产生正确的行为,你最终将得到大约两倍于我给出的 asprintf()
代码,并基本上重新编写 asprintf()
的功能。
如果你想提供一个包装器来接收 printf()
风格参数列表的 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");
}
}
asprintf()
既不是标准C 2011的一部分,也不是POSIX的一部分,甚至不是POSIX 2008或2013的一部分。它是TR 27431-2的一部分:请参见Do you use the TR 24731 'safe' functions?。 - Jonathan Leffler我认为你想要能够轻松地将使用 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()
系列函数的平台,我成功地使用了 Holger Weiss 的一个几乎是公共领域的 snprintf()
。
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()
返回的长度是否包括终止符或不包括。验证和总结:
asprintf
= malloc
+ sprintf
int largeEnoughBufferLen = 20;
char *someStr = (char*)malloc(largeEnoughBufferLen * sizeof(char));
sprintf(someStr, "formatted string: %s %s!", "Hello", "world");
// do what you want for formatted string: someStr
free(someStr);
char *someStr;
int formattedStrResult = asprintf(&someStr, "formatted string: %s %s!", "Hello", "world");
if(formattedStrResult > 0){
// do what you want for formatted string: someStr
free(someStr);
} else {
// some error
}
我没有做过这个,所以我只是指出正确的答案。
C语言提供了使用<stdarg.h>
头文件来处理不确定数量操作数的函数。你可以将你的函数定义为void log_out(const char *fmt, ...);
,并在函数内部获取va_list
。然后你可以分配内存,并使用分配的内存、格式和va_list
调用vsprintf()
。
或者,你可以使用这种方法编写类似于sprintf()
的函数,该函数将分配内存并返回格式化的字符串,生成方式与上述类似。虽然会有内存泄漏,但如果你只是记录日志,这可能并不重要。
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,并提供足够的缓冲区。