【问题标题】:C variadic wrapperC 可变参数包装器
【发布时间】:2016-06-21 14:28:24
【问题描述】:

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

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

谢谢。

【问题讨论】:

  • 如果你打算写给stdout,为什么不直接使用vprintf呢?如果您想写入文件(使用vfprintf),则同样如此。然后,您的代码中根本没有缓冲区分配,您不必处理确定有多少参数或缓冲区有多大。
  • 您忽略了传递的参数之一不是通过char * 传递的以 NUL 结尾的字符串的可能性。如果您收到intdouble 怎么办?
  • ... 或带有 %p 的 NULL
  • 原因是,你不能从这个简化的例子中看出,这个函数不仅仅是简单地吐出缓冲区。它还添加了一个时间戳,并通过一个额外的参数,根据该参数添加了一些 ANSI 着色 ;-)。但我想保持示例简单;-)

标签: c wrapper variadic


【解决方案1】:

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

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

【讨论】:

  • 好主意,但有些系统不能使用args 两次(例如,IBM Z 系列上的 linux),因此您应该添加 va_copy()
  • 一个非常可靠的解决方案,经过测试,它开箱即用:D。
【解决方案2】:

我做错了什么?

传递va_list arg

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

不等于传几个char*

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

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


替代方案是

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

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

    • 鉴于目标平台是“一个小型嵌入式平台”,我建议 OP 在使用 at 之前验证 (char*)0 实际上是正在使用的编译器的 NULL 指针.我不认为 (char*)0 被要求在 C11 之前成为有效的 NULL 指针。
    • @AndrewHenle,您的意思可能是空指针而不是 NULL 指针。 (char*)0 不是标准意义上的“空指针常量”,在这里您实际上需要(void*)0。但是对于手头的情况,这无关紧要,它是程序所期望的正确类型的空指针。
    • 它有时会工作,有时会崩溃.. 看起来取决于参数的数量.. 还不能真正理解为什么。
    猜你喜欢
    • 1970-01-01
    • 2015-09-20
    • 2022-11-05
    • 1970-01-01
    • 2020-07-31
    • 1970-01-01
    • 2016-04-20
    • 2012-09-27
    • 1970-01-01
    相关资源
    最近更新 更多