Gfortran,bindc ,printf

As FortranTip/printf.f90 at main · Beliavsky/FortranTip · GitHub . The double gives an error output

program call_printf ! GitHub FortranTip printf.f90
    use iso_c_binding, only: c_int, c_char, c_double,c_null_char, c_new_line
    implicit none
    interface
        function print_double(fmt,f) result(ierr) bind(c,name="printf")
            ! Use C printf() to print integer with format fmt
            import c_char,c_int, c_double
            character(c_char) :: fmt(*)
            real(kind=c_double), intent(in), value :: f
            integer(c_int)::ierr
        end function print_double
    end interface
    integer(c_int) :: ierr
    ierr = print_double("double is %f" // c_new_line // c_null_char, 42.0_c_double)
end program call_printf
$(WSL2 gfortran 9.3)   double is 0.000000
$(Win10 gfortran 11.2) double is 0.000000

if use

#include<stdio.h>
int print(char * s,double d){
   return printf(s,d);
}

the result is correct. Is it the bug for iso_c_binding for gfortran?

I also wonder what is happening. I added function to print floats and doubles to my original code:

program call_printf
use iso_c_binding, only: c_int, c_float, c_double, c_char, c_null_char, c_new_line
interface
function print_string(fmt,s) result(ierr) bind(c,name="printf")
! Use C printf() to print string with format fmt
import c_char
character(c_char) :: fmt(*), s(*)
end function print_string
!
function print_integer(fmt,i) result(ierr) bind(c,name="printf")
! Use C printf() to print integer with format fmt
import c_char, c_int
character(c_char) :: fmt(*)
integer(kind=c_int), intent(in), value :: i
end function print_integer
!
function print_float(fmt,x) result(ierr) bind(c,name="printf")
! Use C printf() to print float with format fmt
import c_char, c_float
character(c_char) :: fmt(*)
real(kind=c_float), intent(in), value :: x
end function print_float
!
function print_double(fmt,x) result(ierr) bind(c,name="printf")
! Use C printf() to print double with format fmt
import c_char, c_double
character(c_char) :: fmt(*)
real(kind=c_double), intent(in), value :: x
end function print_double
!
end interface
integer :: ierr
ierr = print_string("string is %s" // c_new_line // c_null_char, &
                    "abc" // c_null_char)
ierr = print_integer("int is %d" // c_new_line // c_null_char, 42_c_int)
ierr = print_float("float is %f" // c_new_line // c_null_char, 3.14_c_float)
ierr = print_double("double is %f" // c_new_line // c_null_char, 3.14_c_double)
end program call_printf

Compiling and running on Windows with gfortran or ifort gives output

string is abc
int is 42
float is 0.000000
double is 0.000000

but on WSL2, gfortran gives

string is abc
int is 42
float is 0.000000
double is 3.140000

while ifort gives the same output as on Windows.

As @FortranFan remarked in his previous post, variadic functions are not interoperable. The prototype of printf is this:

int printf ( const char * format, ... );

If you go searching for a function called printf in glibc, you won’t find it. What you will find is this:

int
__printf (const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = vfprintf (stdout, format, arg);
  va_end (arg);

  return done;
}

A macro is used to associate printf to __printf

ldbl_strong_alias (__printf, printf);

where the macro is

#define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)

As you can see the macro just calls another macro, defined somewhere else. I’ve found a blog on " Where the printf() Rubber Meets the Road" which explains how printf works. You can also start in glibc/stdio-common/printf.c and work your way down.

If you search for printf in libc there is a matching symbol:

Click to expand
$ nm -D /lib/x86_64-linux-gnu/libc.so.6 | grep printf
0000000000061f20 T __asprintf
0000000000061f20 W asprintf
000000000012f640 T __asprintf_chk
0000000000061fe0 T dprintf
000000000012f720 T __dprintf_chk
0000000000061c00 T fprintf
000000000012e0b0 T __fprintf_chk
0000000000086450 W fwprintf
000000000012f0d0 T __fwprintf_chk
0000000000061c00 W _IO_fprintf
0000000000061cc0 T _IO_printf
0000000000061e50 T _IO_sprintf
000000000005b8d0 T _IO_vfprintf
0000000000085160 T _IO_vsprintf
000000000008c530 W obstack_printf
000000000012f800 T __obstack_printf_chk
000000000008c360 W obstack_vprintf
000000000012f8c0 T __obstack_vprintf_chk
000000000005ecf0 T parse_printf_format
0000000000061cc0 T printf
000000000012dfe0 T __printf_chk
000000000005ea00 T __printf_fp
0000000000061130 T printf_size
0000000000061be0 T printf_size_info
...

But I doubt it’s what you expect it to be.

I also doubt it’s a good idea to define the C-formatted string literal in Fortran. printf stands for formatted printing. If you call printf in C, your compiler will check the arguments match the string literal. For example:

/* test_printf.c */
#include <stdio.h>

int main(int argc, char const *argv[])
{
    printf("%f\n", 42);
    return 0;
}
$ gcc test_printf.c 
test_printf.c: In function ‘main’:
test_printf.c:6:18: warning: format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘int’ [-Wformat=]
    6 |         printf("%f\n", 42);
      |                 ~^     ~~
      |                  |     |
      |                  |     int
      |                  double
      |                 %d

If you’d like to use printf for some reason, pass the arguments to C, and then call printf on the C side.

1 Like

Also, all float arguments to printf are promoted to double by the compiler, so you probably cannot pass c_float in print_float