Search code examples
cwrappervariadic

C variadic wrapper


To output formatted debug output, I've written a wrapper for vsfprint. Now, I wanted to allocate exactly enough memory for the output buffer, instead of just claiming a random high buffer size (it's a small embedded platform (ESP8266)). For that I iterate through the variable arguments until a NULL is found.

This works fine, provided that I don't forget to add a (char *)NULL parameter to every call. So, I thought, let create another wrapper, a function that just relays all arguments and adds a (char *) NULL parameter:

#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;
}

Calling the write_log() function directly works fine (if you don't forget the NULL parameter). Calling the write_log_wrapper() function will only display the first paramter, and then adds a "(nu" (garbage?) to the output.

What am I doing wrong? Is this a good way to approach what I'm aiming to do in the first place?

Thanks.


Solution

  • To determine how big a buffer is needed to hold the output string, you need to fully parse the entire format string and actually expand the arguments.

    You can either do it yourself, duplicating all the processing of printf() and its ilk and hoping to not make any mistakes, or you can use vsnprintf() - first to determine the size, and then to actually expand the inputs to one output string.

    #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;
    }