Search code examples
cvariadic-functionsvariadic

vsnprintf with a null destination pointer unexpected answer


Main Issue

I'm getting some unexpected result when using vsnprintf. In the code below, I used snprintf and passed a null destination pointer to find out how much space it needs

#define long_string 256
typedef char STRING_VARIABLE [long_string + 1];

void SAFE_snprintf( char * buffer, char * format ,...  )
{
  va_list args;

  va_start(args, format);

  int m = vsnprintf (0, 0, format, args);
  printf("m = %d\n", m);
  if (m < 0)
    {
    perror ("snprintf failed");
    abort ();
    }

  // Allocating memory
  char *bufferString = (char *) malloc (n - 1);
  if (!bufferString)
    {
    perror ("malloc failed");
    abort ();
    } 

  m = vsnprintf (bufferString, (n - 1), format, args);

 if (m < 0)
    {
    perror ("vsnprintf failed");
    abort ();
    }

  strcpy(buffer, bufferString);

  free (bufferString);

  va_end(args);

}

int main(int argc, char * argv[])
{
  char InputString [] = "Hello";
  STRING_VARIABLE bufferStrings;
  char format [] = "%s_test";
  int n = snprintf (0, 0, "%s_test", InputString);

  if (n < 0)
    {
    perror ("vsnprintf failed");
    abort ();
    }

  printf("n = %d", n);
  

  SAFE_snprintf(bufferStrings, format , InputString);
  
  return 0;
}

The above code returns

n = 7
m = 10

I'm not sure why snprintf is returning 7 (which is correct), and vsnprintf is returning 10. Which I assume is wrong or of course my understanding is flawed somewhere.

Why I am doing this?

I wanted to use snprintf "safely" i.e. by avoiding string truncation. The idea was to determine the string size before using snprintf. This meant using it twice. Once to workout the size, then allocate appropriate memory to use snprintf again. Ofcourse there are a variable amount of inputs needed as any printf functions. So wanted to use vsnprintf to create a variadic function which does the aforementioned.

I know there is still the issue of checking if the original string being passed isn't too long and will not result in string truncation. But confused to why vsnprintf is not working as expected


Solution

  • It's invalid to re-use va_list for another call.

    va_list args;
    va_start(args, format);
    vsnprintf(..., args);
    va_end(args); // basically you have to call it
    

    Call va_copy or va_start after it again before calling another v* function.

    va_list args;
    va_start(args, format);
    vsnprintf(..., args);
    va_end(args);
    va_start(args, format);
    vsnprintf(..., args);
    va_end(args);
    

    or

    va_list args, args2;
    va_start(args, format);
    vsnprintf(..., args);
    va_copy(args2, args);
    va_end(args);
    vsnprintf(..., args2);
    va_end(args2);
    

    Your buffer is limited to 256 characters long_string 256, so it does not solve any problem and strcpy(buffer, bufferString); is very unsafe.

    Do not use typedef arrays - they are very confusing. Prefer a structure.

    Overall, you seem to want to implement asprintf - see sprintf() with automatic memory allocation? , https://man7.org/linux/man-pages/man3/asprintf.3.html . Maybe asprintf is going to be standard https://en.cppreference.com/w/c/experimental/dynamic .