C variadic包装

时间:2016-06-21 14:28:24

标签: c wrapper variadic

为了输出格式化的调试输出,我为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”(垃圾?)。

我做错了什么?这是一个很好的方法来接近我的目标吗?

感谢。

3 个答案:

答案 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也适用于这种特殊情况。)