As I found out, when passing through ..., a C compiler will promote incoming args - integers to be no less than int size and floats to be no less than double size. Crystal apparently fails to do that. See here: a basic function accepting varargs and printing the first one as float. When called from C, the float gets promoted to double. But Crystal doesn't promote and the result ends up being zero.
{% `echo '#include <stdio.h>
#include <stdarg.h>
void f(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
printf("!!! %f\\n", va_arg(args, double));
va_end(args);
}
void g() {
f("unused", 1.23f);
}
' > foo.c &&
gcc -c foo.c -o /tmp/foo.o` %}
@[Link(ldflags: "/tmp/foo.o")]
lib Lib
fun f(fmt : LibC::Char*, ...)
fun g()
end
Lib.f("unused", 1.23f32)
Lib.g()
!!! 0.000000
!!! 1.230000
Note that it's not a mistake in my example that I read the arg as double.
Output if doing otherwise:
In file included from foo.c:2:
foo.c: In function 'f':
foo.c:6:45: warning: 'float' is promoted to 'double' when passed through '...'
6 | printf("!!! %f\n", va_arg(args, float));
| ^
foo.c:6:45: note: (so you should pass 'double' not 'float' to 'va_arg')
foo.c:6:45: note: if this code is reached, the program will abort
Program received and didn't handle signal ILL (4)
float through va_args, you have to pass it as a double or otherwise.Well, you can't forget something you never knew 😁
Since this observation came to be observed experimentally, I wonder if this is somewhere defined in the ANSI C standard or otherwise? In particular I wonder if the observations made are complete, or there's more things to handle, and whether they're platform or architecture specific or not.
Yes this is part of the standard.
https://stackoverflow.com/questions/1255775/default-argument-promotions-in-c-function-calls
https://unspecified.wordpress.com/2009/08/31/default-argument-types-in-c/
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
If the expression that denotes the called function has a type that does
not include a prototype, the integer promotions are performed on each
argument, and arguments that have type float are promoted to double.
These are called the default argument promotions.
— An object or expression with an integer type whose integer conversion
rank is less
than or equal to the rank of int and unsigned int.— A bit-field of type _Bool, int, signed int, or unsigned int.
If an int can represent all values of the original type, the value is
converted to an int;
otherwise, it is converted to an unsigned int. These are called the
integer
promotions.
All other types are unchanged by the integer promotions.
@asterite I'd suggest adding above links as comments in the code.
The code is already linked to these issue. From the issue you can go to the details.
@oprypin Great, thank you for digging that up!
@asterite It's not obvious when you're reading just the code, but hey, _no pasa nada_ :)