我有一个接受字符串的函数,即:
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);
有什么方法可以缩短这个吗?
答案 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,您需要提供足够的缓冲区来打印。