C可变包装器

C variadic wrapper

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

我做错了什么?这是实现我最初的目标的好方法吗?

谢谢。

What am I doing wrong?

传递一个 va_list arg

write_log(format, arg, (char *)NULL);

不等于传递几个char*

write_log("Welcome to %s%s", sDeviceName, sFiller1, (char *)NULL);

您不会绕过标记传递的参数结束的标记,即 (char*) NULL 或您决定使用的任何内容。


备选方案是

  • 显式传递参数的数量,可能作为第二个参数
  • 解析转换说明符的格式字符串,实际上是模仿 printf 所做的。

要确定需要多大的缓冲区来保存输出字符串,您需要完全解析整个格式字符串并实际展开参数

您可以自己做,重复 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;
}

如果您只想确保所有调用都在最后收到一个 setinel,请使用宏:

#define WRITE_LOG(...) write_log(__VA_ARGS__, (char*)0)

这样可以确保最后总是有一个额外的 0

还要小心NULL。在 C 标准中未指定此解析的表达式。常见的情况是 0(void*)0。所以在 64 位架构上,它们可能具有不同的宽度(第一个是 32 位,第二个是 64 位)。可变参数函数在这里接收到错误的宽度可能是致命的。因此,我使用了 (char*)0 ,这是您的函数似乎期望的类型。 (但 (void*)0 也适用于这种特殊情况。)