Trouble with understanding bind(C)

I’m trying to understand the behaviour of bind(C) which I don’t really get when applied to functions or variables.
When I compile a static library, functions and variables with bind(C) will be global (symbol types T and D) even if public is not used.
However, when I create a shared library from the static one:

  • functions with bind(C) will be global (symbol type T) even if I did not use public.
  • variables (might be protected) will be local (symbol type d) except if I use public.

Did you have a similar experience? I’m really interested in understanding why is it working this way?

N.B: Very often, I create shared libraries from the static libraries generated by fpm. I would like to avoid public for C API functions and variables to avoid polluting the Fortran side but still make C API functions and variables global.

Just a few remarks:

  • Functions, subroutines and module variables are public by default. You need to use the private attribute or statement explicitly to hide them.
  • To ensure that bind(C) routines are not visible on the Fortran side, you might use a mixed-case C name. As Fortran is case-insensitive, a name like “FoO” is very unlikely to be visible. Fortran compilers sometimes use all-lowercase or all-uppercase names for their symbols, but I have never seen one with a mixture.

As I said though, just a few remarks.

I’d recommend having a closer look at the option -fvisibility=..., which controls the visibility of the symbols in the archive.

The article by Ulrich Drepper, How To Write Shared Libraries, gives some advice on this topic in Section 2.2.2, page 18.

Perhaps you can try compiling with -fvisibility=hidden, and then check the symbols you expect are exported with nm -D libfoo.so?

2 Likes

Perhaps it helps to think of this in C language terms. Variables and procedures with bind(C) have external linkage, meaning the symbol is visible throughout the whole program.

It has to work this way for interoperability with C:

// foo.c
int bar = 42;
! foo.f90
module foo
use, intrinsic :: iso_c_binding, only: c_int
implicit none
private
integer(c_int), bind(c,name="bar") :: bar
end module

The private and public attributes in Fortran apply when importing from a module. But nothing prevents you from declaring a second module that binds to the same variable:

! foo.f90
module foo2
use, intrinsic :: iso_c_binding, only: c_int
implicit none
private
integer(c_int), bind(c,name="bar") :: bar   ! same bar as in foo
end module
1 Like

Just a related question: I’ve just tried this code (for learning C-interoperability),

!! lib1.f90
module test1_m
    use iso_c_binding
    private
    public show1

    integer(c_int), bind(c,name="bar") :: bar = 100_c_int
contains
    subroutine show1(); print *, bar; end
end module


!! lib2.f90
module test2_m
    use iso_c_binding
    private
    public show2

    integer(c_int), bind(c,name="bar") :: bar = 200_c_int
contains
    subroutine show2(); print *, bar; end
end module

!! main.f90
program main
    use test1_m
    use test2_m
    implicit none

    call show1()
    call show2()
end

But compilartion with gfortran-10 gives (on my mac):

$ gfortran lib1.f90 lib2.f90 main.f90 
duplicate symbol _bar in:
    /var/folders/9v/gx_yp_3913b9_kc8rzw0vtqc0000jb/T//cccB55Jw.o
    /var/folders/9v/gx_yp_3913b9_kc8rzw0vtqc0000jb/T//ccFVEx25.o
ld: 1 duplicate symbol for architecture x86_64
collect2: error: ld returned 1 exit status

with

$ gfortran -c lib1.f90
$ nm lib1.o
...
0000000000000074 D _bar
(similar for lib2.o)

So I wonder the two modules define the same symbol twice? (in contrast to extern vs definition in C?)

EDIT: If I remove the initialization (100_c_int and 200_c_int), the code seems to compile without error. Also, if I remove only 200_c_int, the code compiles and gives 100 100. So if I do not give the initial value, it works as extern thing in C…?

Short answer - yes. You cannot perform the definition (initialization) twice.

In C, you will typically see this pattern as you’ve already alluded to:

// lib1.c
int bar = 100;
void show1(void) { printf("%d\n", bar); }
// lib2.c
extern int bar;
void show2(void) { printf("%d\n", bar); }

You often hit this problem if you define something in a header, which gets included from multiple translation units:

// name.h
char* surname = "Backus";

Generally speaking, global data is frowned upon, so if possible avoid using a global variable across multiple units.

1 Like

I prefix all my module variables and/or functions that will be C interoperable with capi_*. This clearly indicates that they are not “pure fortran” variables or functions.

module example
use iso_c_binding
use iso_fortran_env
private

real(real64), parameter, public :: p
real(c_double), protected, bind(C, name="C_name_for_p") :: capi_p = p
! capi_p will not be global in the shared library if I don't specify `public`

contains

function f(args)
...
end function

function capi_f(args)bind(C, name="C_name_for_f")
! This function will be global in the shared library even I did not use `public`
...
end function

From this simple example, I understand that public will imply global (symbol type T) but this is not the case for variables. I don’t really understand the difference between variables and functions when bind(C) is used.

Thanks for the reference. I’ll try to dive deeper in understanding the -fvisibility=hidden option.