创建C格式的字符串(不打印它们)

时间:2021-09-13 21:14:30

I have a function that accepts a string, that is:

我有一个函数接受一个字符串,即

void log_out(char *);

In calling it, I need to create a formatted string on the fly like:

在调用它时,我需要在飞行中创建一个格式化的字符串:

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

How do I do this in ANSI C?

在ANSI C中怎么做呢?


Only, since sprintf() returns a int, this means that I have to write at least 3 commands, like:

只是,因为sprintf()返回一个int,这意味着我必须至少编写3个命令,比如:

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

Any way to shorten this?

有没有办法缩短这个?

7 个解决方案

#1


56  

Use sprintf.

使用sprintf。

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

Write formatted data to string Composes a string with the same text that would be printed if format was used on printf, but instead of being printed, the content is stored as a C string in the buffer pointed by str.

将格式化后的数据写入字符串,该字符串与将在printf中使用格式的相同文本的字符串相匹配,但是,内容以C字符串的形式存储在str所指向的缓冲区中,而不是打印出来。

The size of the buffer should be large enough to contain the entire resulting string (see snprintf for a safer version).

缓冲区的大小应该足够大,以容纳整个生成的字符串(请参阅snprintf以获得更安全的版本)。

A terminating null character is automatically appended after the content.

在内容之后自动附加一个终止null字符。

After the format parameter, the function expects at least as many additional arguments as needed for format.

在格式化参数之后,函数希望至少需要更多的格式参数。

Parameters:

str

Pointer to a buffer where the resulting C-string is stored. The buffer should be large enough to contain the resulting string.

指向一个缓冲区的指针,在该缓冲区中存储生成的c字符串。缓冲区应该足够大,以包含产生的字符串。

format

C string that contains a format string that follows the same specifications as format in printf (see printf for details).

包含与printf中格式相同的格式字符串(详见printf)的格式字符串。

... (additional arguments)

Depending on the format string, the function may expect a sequence of additional arguments, each containing a value to be used to replace a format specifier in the format string (or a pointer to a storage location, for n). There should be at least as many of these arguments as the number of values specified in the format specifiers. Additional arguments are ignored by the function.

根据格式字符串,函数可能期望一个附加参数序列,每个都包含一个值被用来替代格式字符串的格式说明符(或指针存储位置,n)。至少应该有尽可能多的这些参数的值中指定的格式说明符。函数会忽略其他参数。

Example:

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

#2


9  

It sounds to me like you want to be able to easily pass a string created using printf-style formatting to the function you already have that takes a simple string. You can create a wrapper function using stdarg.h facilities and vsnprintf() (which may not be readily available, depending on your compiler/platform):

在我看来,您希望能够轻松地将使用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;
}

For platforms that don't provide a good implementation (or any implementation) of the snprintf() family of routines, I've successfully used a nearly public domain snprintf() from Holger Weiss.

对于那些不能很好地实现snprintf()的例程系列的平台,我已经成功地使用了来自Holger Weiss的一个公共域snprintf()。

#3


9  

If you have a POSIX-2008 compliant system (any modern Linux), you can use the safe and convenient asprintf() function: It will malloc() enough memory for you, you don't need to worry about the maximum string size. Use it like this:

如果您有一个POSIX-2008兼容系统(任何现代Linux),您可以使用安全且方便的asprintf()函数:它将为您提供足够的内存,您不必担心最大字符串大小。使用它是这样的:

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

This is the minimum effort you can get to construct the string in a secure fashion. The sprintf() code you gave in the question is deeply flawed:

这是您能够以安全的方式构造字符串的最小努力。您在问题中给出的sprintf()代码存在严重缺陷:

  • There is no allocated memory behind the pointer. You are writing the string to a random location in memory!

    指针后面没有分配的内存。您将字符串写入内存中的随机位置!

  • Even if you had written

    即使你已经写好了。

    char s[42];
    

    you would be in deep trouble, because you can't know what number to put into the brackets.

    你可能会深陷麻烦,因为你不知道把什么数字放进括号里。

  • Even if you had used the "safe" variant snprintf(), you would still run the danger that your strings gets truncated. When writing to a log file, that is a relatively minor concern, but it has the potential to cut off precisely the information that would have been useful. Also, it'll cut off the trailing endline character, gluing the next log line to the end of your unsuccessfully written line.

    即使您使用了“安全”的变体snprintf(),您仍然会遇到您的字符串被截断的危险。在写入日志文件时,这是一个相对较小的问题,但是它有可能精确地切断那些有用的信息。此外,它将切断拖尾的endline字符,将下一个日志行粘贴到未成功写入行的末尾。

  • If you try to use a combination of malloc() and snprintf() to produce correct behavior in all cases, you end up with roughly twice as much code than I have given for asprintf(), and basically reprogram the functionality of asprintf().

    如果您尝试使用malloc()和snprintf()的组合来在所有情况下生成正确的行为,那么您得到的代码大约是我为asprintf()提供的代码的两倍,并且基本上重新编程了asprintf()的功能。


If you are looking at providing a wrapper of log_out() that can take a printf() style parameter list itself, you can use the variant vasprintf() which takes a va_list as an argument. Here is a perfectly safe implementation of such a wrapper:

如果您正在考虑提供一个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");
    }
}

#4


3  

If you have the code to log_out(), rewrite it. Most likely, you can do:

如果您有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);
}

If there is extra logging information needed, that can be printed before or after the message shown. This saves memory allocation and dubious buffer sizes and so on and so forth. You probably need to initialize logfp to zero (null pointer) and check whether it is null and open the log file as appropriate - but the code in the existing log_out() should be dealing with that anyway.

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

The advantage to this solution is that you can simply call it as if it was a variant of printf(); indeed, it is a minor variant on printf().

这个解决方案的优点是,您可以简单地将其称为printf()的变体;实际上,它是printf()的一个小变体。

If you don't have the code to log_out(), consider whether you can replace it with a variant such as the one outlined above. Whether you can use the same name will depend on your application framework and the ultimate source of the current log_out() function. If it is in the same object file as another indispensable function, you would have to use a new name. If you cannot work out how to replicate it exactly, you will have to use some variant like those given in other answers that allocates an appropriate amount of memory.

如果您没有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? */
}

Obviously, you now call the log_out_wrapper() instead of log_out() - but the memory allocation and so on is done once. I reserve the right to be over-allocating space by one unnecessary byte - I've not double-checked whether the length returned by vsnprintf() includes the terminating null or not.

显然,现在您调用log_out_wrapper()而不是log_out()——但是内存分配和诸如此类的操作一次就完成了。我保留使用一个不必要的字节来过度分配空间的权利——我没有重复检查vsnprintf()返回的长度是否包括终止null。

#5


0  

I haven't done this, so I'm just going to point at the right answer.

我没有做过这个,所以我要指向正确的答案。

C has provisions for functions that take unspecified numbers of operands, using the <stdarg.h> header. You can define your function as void log_out(const char *fmt, ...);, and get the va_list inside the function. Then you can allocate memory and call vsprintf() with the allocated memory, format, and va_list.

使用 头。您可以将您的函数定义为void log_out(const char *fmt,…),并在函数中获取va_list。然后,您可以使用分配的内存、格式和va_list分配内存和调用vsprintf()。

Alternately, you could use this to write a function analogous to sprintf() that would allocate memory and return the formatted string, generating it more or less as above. It would be a memory leak, but if you're just logging out it may not matter.

或者,您可以使用这个函数来编写一个类似sprintf()的函数,该函数将分配内存并返回格式化的字符串,生成的字符串或多或少。这将是一个内存泄漏,但如果您只是退出,可能并不重要。

#6


-1  

Don't use sprintf.
Look into the URL-Textfield of your Webbrowser.
It will overflow your Stack!
Use snprintf

不要使用sprintf。查看你的网络浏览器的URL-Textfield。它会溢出你的堆栈!使用snprintf

#7


-2  

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html gives the following example to print to stderr. You can modify it to use your log function instead:

http://www.gnu.org/software/hello/manual/libc/variablearguments-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);
 }

Instead of vfprintf you will need to use vsprintf where you need to provide an adequate buffer to print into.

而不是vfprintf,您需要使用vsprintf,您需要提供足够的缓冲区来打印。

#1


56  

Use sprintf.

使用sprintf。

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

Write formatted data to string Composes a string with the same text that would be printed if format was used on printf, but instead of being printed, the content is stored as a C string in the buffer pointed by str.

将格式化后的数据写入字符串,该字符串与将在printf中使用格式的相同文本的字符串相匹配,但是,内容以C字符串的形式存储在str所指向的缓冲区中,而不是打印出来。

The size of the buffer should be large enough to contain the entire resulting string (see snprintf for a safer version).

缓冲区的大小应该足够大,以容纳整个生成的字符串(请参阅snprintf以获得更安全的版本)。

A terminating null character is automatically appended after the content.

在内容之后自动附加一个终止null字符。

After the format parameter, the function expects at least as many additional arguments as needed for format.

在格式化参数之后,函数希望至少需要更多的格式参数。

Parameters:

str

Pointer to a buffer where the resulting C-string is stored. The buffer should be large enough to contain the resulting string.

指向一个缓冲区的指针,在该缓冲区中存储生成的c字符串。缓冲区应该足够大,以包含产生的字符串。

format

C string that contains a format string that follows the same specifications as format in printf (see printf for details).

包含与printf中格式相同的格式字符串(详见printf)的格式字符串。

... (additional arguments)

Depending on the format string, the function may expect a sequence of additional arguments, each containing a value to be used to replace a format specifier in the format string (or a pointer to a storage location, for n). There should be at least as many of these arguments as the number of values specified in the format specifiers. Additional arguments are ignored by the function.

根据格式字符串,函数可能期望一个附加参数序列,每个都包含一个值被用来替代格式字符串的格式说明符(或指针存储位置,n)。至少应该有尽可能多的这些参数的值中指定的格式说明符。函数会忽略其他参数。

Example:

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

#2


9  

It sounds to me like you want to be able to easily pass a string created using printf-style formatting to the function you already have that takes a simple string. You can create a wrapper function using stdarg.h facilities and vsnprintf() (which may not be readily available, depending on your compiler/platform):

在我看来,您希望能够轻松地将使用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;
}

For platforms that don't provide a good implementation (or any implementation) of the snprintf() family of routines, I've successfully used a nearly public domain snprintf() from Holger Weiss.

对于那些不能很好地实现snprintf()的例程系列的平台,我已经成功地使用了来自Holger Weiss的一个公共域snprintf()。

#3


9  

If you have a POSIX-2008 compliant system (any modern Linux), you can use the safe and convenient asprintf() function: It will malloc() enough memory for you, you don't need to worry about the maximum string size. Use it like this:

如果您有一个POSIX-2008兼容系统(任何现代Linux),您可以使用安全且方便的asprintf()函数:它将为您提供足够的内存,您不必担心最大字符串大小。使用它是这样的:

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

This is the minimum effort you can get to construct the string in a secure fashion. The sprintf() code you gave in the question is deeply flawed:

这是您能够以安全的方式构造字符串的最小努力。您在问题中给出的sprintf()代码存在严重缺陷:

  • There is no allocated memory behind the pointer. You are writing the string to a random location in memory!

    指针后面没有分配的内存。您将字符串写入内存中的随机位置!

  • Even if you had written

    即使你已经写好了。

    char s[42];
    

    you would be in deep trouble, because you can't know what number to put into the brackets.

    你可能会深陷麻烦,因为你不知道把什么数字放进括号里。

  • Even if you had used the "safe" variant snprintf(), you would still run the danger that your strings gets truncated. When writing to a log file, that is a relatively minor concern, but it has the potential to cut off precisely the information that would have been useful. Also, it'll cut off the trailing endline character, gluing the next log line to the end of your unsuccessfully written line.

    即使您使用了“安全”的变体snprintf(),您仍然会遇到您的字符串被截断的危险。在写入日志文件时,这是一个相对较小的问题,但是它有可能精确地切断那些有用的信息。此外,它将切断拖尾的endline字符,将下一个日志行粘贴到未成功写入行的末尾。

  • If you try to use a combination of malloc() and snprintf() to produce correct behavior in all cases, you end up with roughly twice as much code than I have given for asprintf(), and basically reprogram the functionality of asprintf().

    如果您尝试使用malloc()和snprintf()的组合来在所有情况下生成正确的行为,那么您得到的代码大约是我为asprintf()提供的代码的两倍,并且基本上重新编程了asprintf()的功能。


If you are looking at providing a wrapper of log_out() that can take a printf() style parameter list itself, you can use the variant vasprintf() which takes a va_list as an argument. Here is a perfectly safe implementation of such a wrapper:

如果您正在考虑提供一个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");
    }
}

#4


3  

If you have the code to log_out(), rewrite it. Most likely, you can do:

如果您有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);
}

If there is extra logging information needed, that can be printed before or after the message shown. This saves memory allocation and dubious buffer sizes and so on and so forth. You probably need to initialize logfp to zero (null pointer) and check whether it is null and open the log file as appropriate - but the code in the existing log_out() should be dealing with that anyway.

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

The advantage to this solution is that you can simply call it as if it was a variant of printf(); indeed, it is a minor variant on printf().

这个解决方案的优点是,您可以简单地将其称为printf()的变体;实际上,它是printf()的一个小变体。

If you don't have the code to log_out(), consider whether you can replace it with a variant such as the one outlined above. Whether you can use the same name will depend on your application framework and the ultimate source of the current log_out() function. If it is in the same object file as another indispensable function, you would have to use a new name. If you cannot work out how to replicate it exactly, you will have to use some variant like those given in other answers that allocates an appropriate amount of memory.

如果您没有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? */
}

Obviously, you now call the log_out_wrapper() instead of log_out() - but the memory allocation and so on is done once. I reserve the right to be over-allocating space by one unnecessary byte - I've not double-checked whether the length returned by vsnprintf() includes the terminating null or not.

显然,现在您调用log_out_wrapper()而不是log_out()——但是内存分配和诸如此类的操作一次就完成了。我保留使用一个不必要的字节来过度分配空间的权利——我没有重复检查vsnprintf()返回的长度是否包括终止null。

#5


0  

I haven't done this, so I'm just going to point at the right answer.

我没有做过这个,所以我要指向正确的答案。

C has provisions for functions that take unspecified numbers of operands, using the <stdarg.h> header. You can define your function as void log_out(const char *fmt, ...);, and get the va_list inside the function. Then you can allocate memory and call vsprintf() with the allocated memory, format, and va_list.

使用 头。您可以将您的函数定义为void log_out(const char *fmt,…),并在函数中获取va_list。然后,您可以使用分配的内存、格式和va_list分配内存和调用vsprintf()。

Alternately, you could use this to write a function analogous to sprintf() that would allocate memory and return the formatted string, generating it more or less as above. It would be a memory leak, but if you're just logging out it may not matter.

或者,您可以使用这个函数来编写一个类似sprintf()的函数,该函数将分配内存并返回格式化的字符串,生成的字符串或多或少。这将是一个内存泄漏,但如果您只是退出,可能并不重要。

#6


-1  

Don't use sprintf.
Look into the URL-Textfield of your Webbrowser.
It will overflow your Stack!
Use snprintf

不要使用sprintf。查看你的网络浏览器的URL-Textfield。它会溢出你的堆栈!使用snprintf

#7


-2  

http://www.gnu.org/software/hello/manual/libc/Variable-Arguments-Output.html gives the following example to print to stderr. You can modify it to use your log function instead:

http://www.gnu.org/software/hello/manual/libc/variablearguments-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);
 }

Instead of vfprintf you will need to use vsprintf where you need to provide an adequate buffer to print into.

而不是vfprintf,您需要使用vsprintf,您需要提供足够的缓冲区来打印。