为了输出格式化的调试输出,我为vsfprint
编写了一个包装器。现在,我想为输出缓冲区分配足够的内存,而不仅仅是声称一个随机的高缓冲区大小(它是一个小型嵌入式平台(ESP8266))。为此,我遍历变量参数,直到找到NULL。
这样可以正常工作,前提是我不会忘记为每个调用添加(char *)NULL
参数。所以,我想,让我们创建另一个包装器,一个只传递所有参数并添加(char *) NULL
参数的函数:
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // malloc
void write_log(const char *format, ...) {
char* buffdyn;
va_list args;
// CALC. MEMORY
size_t len;
char *p;
if(format == NULL)
return;
len = strlen(format);
va_start(args, format);
while((p = va_arg(args, char *)) != NULL)
len += strlen(p);
va_end(args);
// END CALC. MEMORY
// ALLOCATE MEMORY
buffdyn = malloc(len + 1); /* +1 for trailing \0 */
if(buffdyn == NULL) {
printf("Not enough memory to process message.");
return;
}
va_start(args, format);
//vsnprintf = Write formatted data from variable argument list to sized buffer
vsnprintf(buffdyn, len, format, args);
va_end(args);
printf("%s\r\n",buffdyn);
free(buffdyn);
}
void write_log_wrapper(const char *format, ...) {
va_list arg;
va_start(arg, format);
write_log(format,arg,(char *)NULL);
va_end(arg);
}
int main()
{
const char* sDeviceName = "TEST123";
const char* sFiller1 = "12345678";
write_log_wrapper("Welcome to %s%s", sDeviceName,sFiller1);
write_log("Welcome to %s%s", sDeviceName,sFiller1, (char *)NULL);
return 0;
}
直接调用write_log()
函数可以正常工作(如果你没有忘记NULL参数)。调用write_log_wrapper()
函数只会显示第一个参数,然后在输出中添加“(nu”(垃圾?)。
我做错了什么?这是一个很好的方法来接近我的目标吗?
感谢。
答案 0 :(得分:3)
要确定保存输出字符串需要多大的缓冲区,您需要完全解析整个格式字符串并实际扩展参数。
你可以自己做,复制printf()
及其同类的所有处理并希望不犯任何错误,或者你可以使用vsnprintf()
- 首先确定大小,然后再实际上将输入扩展为一个输出字符串。
#define FIXED_SIZE 64
void write_log(const char *format, ...)
{
// set up a fixed-size buffer and a pointer to it
char fixedSizeBuffer[ FIXED_SIZE ];
char *outputBuffer = fixedSizeBuffer;
// no dynamic buffer yet
char *dynamicBuffer = NULL;
// get the variable args
va_list args1;
va_start( args1, format );
// need to copy the args even though we won't know if we
// need them until after we use the first set
va_list args2;
va_copy( args2, args1 );
// have to call vsnprintf at least once - might as well use a small
// fixed-size buffer just in case the final string fits in it
int len = vsnprintf( fixedSizeBuffer, sizeof( fixedSizeBuffer ), format, args1 );
va_end( args1 );
// it didn't fit - get a dynamic buffer, expand the string, and
// point the outputBuffer pointer at the dynamic buffer so later
// processing uses the right string
if ( len > sizeof( fixedSizeBuffer ) )
{
dynamicBuffer = malloc( len + 1 );
vsnprintf( dynamicBuffer, len + 1, format, args2 );
outputBuffer = dynamicBuffer;
}
va_end( args2 );
// do something with outputBuffer
free( dynamicBuffer );
return;
}
答案 1 :(得分:1)
我做错了什么?
传递va_list arg
write_log(format, arg, (char *)NULL);
与传递多个char*
write_log("Welcome to %s%s", sDeviceName, sFiller1, (char *)NULL);
你不会绕过标记传递参数结尾的标记,即(char*) NULL
或你决定使用的任何内容。
替代方案是
printf
的内容。答案 2 :(得分:1)
如果您只想确保所有呼叫最后都收到一个setinel,请使用宏:
#define WRITE_LOG(...) write_log(__VA_ARGS__, (char*)0)
这可以确保最后总是有额外的0
。
还要小心NULL
。在C标准中,它解析了什么表达式。常见情况为0
和(void*)0
。因此,在64位架构上,这些可能具有不同的宽度(第一个为32位,第二个为64位)。可变函数在这里接收错误的宽度可能是致命的。因此我使用(char*)0
这是你的函数似乎期望的类型。 (但(void*)0
也适用于这种特殊情况。)