H Bellamy
H Bellamy

Reputation: 22715

Why does the same vsnprintf code output differently on Windows (MSVC) and Unix (Clang)

On Unix (Clang 3.8.1), this code outputs:

6: 32

8: a8e

On Windows (MSVC 19.00.24215.1), this code outputs:

6: 12345

6: a12345e

#include <iostream>
#include <stdarg.h>

static std::string getFormattedString(const char* fmt, va_list ap) {
  int count = vsnprintf(NULL, 0, fmt, ap) + 1;
  std::cout << count << ": ";
  if (count <= 0) { return "unable to format message"; }

  std::string result = std::string(count, '\0');
  if (vsnprintf(&result[0], count, fmt, ap) < 0) { return "error";}

  return result;
}

static std::string getFormattedString(const char* fmt, ...) {
  va_list ap;
  va_start(ap, fmt);
  std::string result = getFormattedString(fmt, ap);
  va_end(ap);
  return result;
}

int main(int argc, char *argv[]) {
  std::cout << getFormattedString("%d", 12345).c_str() << "\n";
  std::cout << getFormattedString("a%de", 12345).c_str() << "\n";

  return 0;
}

Interestingly, they both get the correct count, but on my Linux and OS X machines, this code outputs an incorrect result. What's causing this? Have I incurred UB somewhere?

Upvotes: 3

Views: 357

Answers (1)

Marc Aldorasi
Marc Aldorasi

Reputation: 708

As @RaymondChen said in the comments, vsnprintf modifies ap. If you want to reuse the va_list, you have to make a copy with va_copy:

static std::string getFormattedString(const char* fmt, va_list ap) {
    va_list ap2;
    va_copy(ap2, ap);
    int count = vsnprintf(NULL, 0, fmt, ap) + 1;
    std::cout << count << ": ";
    if (count <= 0) { return "unable to format message"; }

    std::string result = std::string(count, '\0');
    if (vsnprintf(&result[0], count, fmt, ap2) < 0) { return "error";}
    std::cout << result.size() << ' ' << strlen(result.c_str()) << '\n';

    return result;
}

This will use the original list twice, and produce the correct result.

Upvotes: 4

Related Questions