I’m trying to write a few wrappers for the standard sprintf function from cstdio. However, I’m having some weird behaviour and access violation crashes when running my program. I’ve simplified the problem and reproduced it on the code below:
#include <string>
#include <cstdio>
#include <cstdarg>
std::string vfmt(const std::string& format, va_list args)
{
int size = format.size() * 2;
char* buffer = new char[size];
while (vsprintf(buffer, format.c_str(), args) < 0)
{
delete[] buffer;
size *= 2;
buffer = new char[size];
}
return std::string(buffer);
}
std::string fmt(const std::string& format, ...)
{
va_list args;
va_start(args, format);
std::string res = vfmt(format, args);
va_end(args);
return res;
}
int main()
{
std::string s = fmt("Hello %s!", "world");
printf(s.c_str());
return 0;
}
This code produces a memory access violation when calling vsprintf in vfmt. However, when I change fmt‘s function signature from fmt(const std::string& format, ...) to fmt(const char* format, ...), I no longer crash, and everything works as expected. Why exactly is this happening?
Why is it that changing the type of the format parameter from const std::string& to const char* solves the issue? Or does it only appear to be solved?
You can’t use a reference type as an argument to va_start. That’s why changing to
char*works, and so would leaving thestringbut without the&. Using a reference messes up the magic that is done in order to obtain the variable number of arguments.See Are there gotchas using varargs with reference parameters.