I'm not sure what architecture this is compiled for, I assume it's on your PC.
you call
snprintf ( buffer,
100,
"test two float %0.1f and %0.1f»,
1,
2.2 ) ;
so as explained the compiler puts the parameters on the stack in reverse orders. The compiler has rules for variadic functions or integral literals. 1 is seen as an int
and 2.2 as a floating point number which will be passed as a double. 100 is part of the function's prototype so the compiler knows its type, it's size_t
.
where you get "lucky" it's likely because of compiler padding and memory alignment. the 1 seen as an int
will occupy 4 bytes only but the next argument is a double on 8 bytes and the compiler likely aligns on 64 bits and thus adds 4 0 bytes after the int
if you run this code in your simulator
#include <cstdarg>
#include <iomanip>
#include <iostream>
void printBytes(const void* data, size_t size) {
const unsigned char* bytes = static_cast<const unsigned char*>(data);
for (size_t i = 0; i < size; ++i) {
std::cout << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(bytes[i]) << " ";
}
std::cout << std::dec << std::endl;
}
void variadicFunction(char* buffer, size_t bufferSize, const char* format, ...) {
va_list args;
va_start(args, format);
// Assume the 2 double arguments are on the stack even if we passed an int and a double
double firstDouble = va_arg(args, double);
double secondDouble = va_arg(args, double);
// Print the raw bytes of the doubles
std::cout << "Bytes of first double: ";
printBytes(&firstDouble, sizeof(double));
std::cout << "Bytes of second double: ";
printBytes(&secondDouble, sizeof(double));
// Print the values of the doubles
std::cout << "Value of first double: " << firstDouble << std::endl;
std::cout << "Value of second double: " << secondDouble << std::endl;
// Clean up the va_list
va_end(args);
}
int main() {
char buffer[100];
variadicFunction(buffer, 100, "test two float %0.1f and %0.1f", 1, 2.2);
// Print the formatted string
snprintf(buffer, 100, "test two float %0.1f and %0.1f", 1, 2.2);
std::cout << "Formatted string: " << buffer << std::endl;
return 0;
}
I wrote a variadicFunction
which takes the same format as snprintf() and I look into the stack to mimic what snprintf() would do
you'll see in the console
Bytes of first double: 01 00 00 00 00 00 00 00
Bytes of second double: 9a 99 99 99 99 99 01 40
Value of first double: 4.94066e-324
Value of second double: 2.2
Formatted string: test two float 0.0 and 2.2
what "saved" you is the padding the first double is seen as "01 00 00 00 00 00 00 00" where the first "01 00 00 00 " is the integer 1 (4 bytes) represented in little endian format and then you had 4 padding bytes which makes the second argument (the real double) start at the expected position in the stack and you read "9a 99 99 99 99 99 01 40" which is the IEEE representation of 2.2
if you look at the "01 00 00 00 00 00 00 00" interpreted as a double though, the value is 4.94066e-324 so when snprintf tries to print it using your format %0.1f it's basically 0. if you modify the snprintf() to ask for exponential notation with %e instead of %0.1f
snprintf(buffer, 100, "test two float %e and %0.1f", 1, 2.2);
then you'll see in the console
Formatted string: test two float 4.940656e-324 and 2.2
➜ "01 00 00 00 00 00 00 00" has been interpreted as expected
Now this is on a 64 bit architecture. if you were trying to run this on an ESP32 you would get a totally different behavior. The padding might be gone and the way the stack is used be different.
well - that's what undefined behavior means. You are out of the spec so you get weird unpredictable results.