3

I am launching this snippet:

>>> from ctypes import *
>>> libc = CDLL("libc.so.6")
>>> libc.printf("%f %f\n", c_float(1.0), c_double(1.0))
0.000000 1.000000

printf expects a double for %f BUT I thought floats got promoted to doubles in variadic functions, as the following C code shows:

#include<stdio.h>
int main()
{
   float a = 1.0f;
   double b = 1.0;
   printf("%f %f\n", a, b);
}

produces the expected 1.000000 1.000000.

Am I missing something? Is the compiler doing some implicit casting in the C code?

I am using a 64-bits machine.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
wecx
  • 302
  • 2
  • 10
  • Indeed floats get promoted to doubles in variadic functions as https://stackoverflow.com/questions/8114834/printf-and-casting-float-arguments explains. – wecx Oct 25 '19 at 14:14
  • 1
    The promotion of `float` to `double` is the caller's responsibility. I would guess that ctype does not handle this correctly. – Gerhardh Oct 25 '19 at 14:34
  • 1
    I think what happens is that ctypes doesn't know anything about the signature of `printf` unless you set it with `argtypes` and `restype`. It doesn't know it's variadic, so it's not gonna do the type promotion. As far as ctypes knows, the signature of `printf` is `int printf(char *, float, double)`, since that's how you called it and you didn't set `restype` (`int` is the default return type in `ctypes`). – JBGreen Oct 25 '19 at 14:34
  • @JBChouinard makes perfect sense. Thank you. – wecx Oct 25 '19 at 14:46
  • @Gerhardh just to be sure, in the C snippet above, it's the C compiler that automatically promotes float into double when it builds ? The printf implementation assumes it receives only doubles ? – wecx Oct 25 '19 at 17:15
  • Yes, your expected "floats get promoted to double" is done by the compiler before calling the function. How would `printf` know what type you provide? A called function cannot see what happens in caller function. – Gerhardh Oct 25 '19 at 21:00

1 Answers1

1

The caller is responsible for doing the type promotion from float to double. But ctypes doesn't know anything about the signature of printf unless you tell it. It doesn't know it's variadic and that type promotion is required.

If you don't tell ctypes what the signature of a function is by setting argtypes and restype, it assumes a int return type, and that whatever parameter you passed in match the signatures. So in your case it just assumes the signature of printf is:

int printf(char*, double, float)

As far as I know there is no way to define a variadic argtypes in ctypes, but it doesn't seem to do casting anyway, even if argtypes is set:

>>> from ctypes import *
>>> libc = CDLL("libc.so.6")
>>> libc.printf.argtypes = [c_char_p, c_double, c_double]
>>> libc.printf(c_char_p(b"%f %f\n"), c_double(1.0), c_float(1.0))
Traceback (most recent call last):
  File "printf.py", line 5, in <module>
    libc.printf(c_char_p(b"%f %f\n"), c_double(1.0), c_float(1.0))
ctypes.ArgumentError: argument 3: <class 'TypeError'>: wrong type
JBGreen
  • 534
  • 2
  • 6
  • `ctypes` does coerce to argtypes, but only if you don't pass explicitly. Try `libc.printf(b'%f %f\n',1.0,1)`. Even though using a Python float and integer as parameters, this will work. No need for wrapping the types when argtypes is specified. – Mark Tolonen Oct 25 '19 at 16:15