Search code examples
clinkageinline-functions

Clarification over internal linkage of inline functions in C


As the theory goes, inline functions have internal/static linkage in C, that is, they are only visible within a single translation unit. Hence inline function defined in two separate files should not be able to see each other and both will have their own address space.

I am trying with the following files

***cat a.c*** 

#include <stdio.h>

inline int foo()
{
    return 3;
}

void g()
{
    printf("foo called from g: return value = %d, address = %#p\n", foo(), &foo);
}


***cat b.c***
#include <stdio.h>

inline int foo()
{
    return 4;
}

void g();

int main()
{
    printf("foo called from main: return value = %d, address = %#p\n", foo(), &foo);
    g();
    return 0;
}

gcc -c a.c
gcc -c b.c
gcc -o a.out a.o b.o

b.o: In function `foo':
b.c:(.text+0x0): multiple definition of `foo'
a.o:a.c:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

The above compilation error reflects that b.c is able to see the definition of foo in a.c and compilation is failing (Which should not be the case for internally linked inline functions ).

Please help me to understand if I am missing something.

Edit 1: Was trying out the theory of this link. https://www.ibm.com/support/knowledgecenter/en/ssw_ibm_i_71/rzarg/inline_linkage.htm


Solution

  • TL;DR: GCC still defaults to its old semantics of inline, in which an inline function is still compiled as an externally visible entity. Specifying -std=c99 or -std=c11 will cause GCC to implement the standard semantics; however, the IBM compiler does not conform to the standard either. So linking will still fail, but with a different error.


    Since C99, a function declaration with no declared linkage does not generate a function object. The inline definition will only be used with inline substitution, and the compiler is not obliged to perform this optimisation. It is expected that an external definition of the function exists in some other translation unit, and such a definition must exist if the function object is used, either by taking its address or by being called in a context where the compiler chooses not to perform the inline substitution.

    If the inline function is declared with either static or extern, then a function object is compiled, with the indicated linkage, thereby satisfying the requirement that the function object be defined.

    Prior to C99, inline was not part of the C standard, but many compilers -- particularly GCC -- implemented it as an extension. In the case of GCC, however, the semantics of inline differed slightly from the above exposition.

    In C99 (and more recent), an inline function with no linkage specification is only an inline definition ("An inline definition does not provide an external definition for the function, and does not forbid an external definition in another translation unit." §6.7.4p7). But in the GCC extension, an inline function with no linkage specification was given external linkage (just like a non-inline function declaration). GCC then special-cased extern inline to mean "do not generate a function object", which is effectively the same as standard C99's handling of an inline function with neither extern nor static modifiers. See the GCC manual, particularly the last section.

    This is only still important because GCC still defaults to using its original inline semantics unless you specify that it should conform to some C standard (using, for example, -std=c11) or disable the GNU inline semantics using -fno-gnu89-inline.

    The example code, which I understand is taken from the IBM i7.1 compiler documentation, does not correctly reflect any C standard. The two definitions of foo as inline functions do not generate any actual function named foo, so the use of &foo must refer to some externally-defined foo, and there isn't one in the program. GCC will report this issue if you tell it to use C11/C99 semantics:

    $ gcc -std=c99 a.c b.c
    /tmp/ccUKlp5g.o: In function `g':
    a.c:(.text+0xa): undefined reference to `foo'
    a.c:(.text+0x13): undefined reference to `foo'
    /tmp/cc2hv17O.o: In function `main':
    b.c:(.text+0xa): undefined reference to `foo'
    b.c:(.text+0x13): undefined reference to `foo'
    collect2: error: ld returned 1 exit status
    

    By contrast, if you ask for Gnu inline semantics, both translation units will define foo, and the linker will complain about a duplicate definition:

    $ gcc -std=c99 -fgnu89-inline a.c b.c
    /tmp/ccAHHqOI.o: In function `foo':
    b.c:(.text+0x0): multiple definition of `foo'
    /tmp/ccPyQrTO.o:a.c:(.text+0x0): first defined here
    collect2: error: ld returned 1 exit status
    

    Also note that GCC does not inline any function by default. You must provide some optimization option in order to enable function inlining. If you do so, and you remove the use of the address operator, you can get the program to compile:

    $ cat a2.c
    #include <stdio.h>
    inline int foo() { return 3; }
    void g() {
        printf("foo called from g: return value = %d\n", foo());
    }
    $ cat b2.c
    #include <stdio.h>
    inline int foo() { return 4; }
    void g();
    int main() {
        printf("foo called from main: return value = %d\n", foo());
        g();
        return 0;
    }
    
    $ # With no optimisation, an external definition is still needed:
    $ gcc -std=c11 a2.c b2.c
    /tmp/cccJV9J6.o: In function `g':
    a2.c:(.text+0xa): undefined reference to `foo'
    /tmp/cct5NcjY.o: In function `main':
    b2.c:(.text+0xa): undefined reference to `foo'
    collect2: error: ld returned 1 exit status
    
    $ # With inlining enabled, the program works as (possibly) expected:
    $ gcc -std=c11 -O a2.c b2.c
    $ gcc -std=c11 -O1 a2.c b2.c
    $ ./a.out
    foo called from main: return value = 4
    foo called from g: return value = 3
    

    As indicated by the IBM documentation, the rules for C++ are distinct. This program is not valid C++ because the definitions of foo in the two translation units differ, but the compiler is not obliged to detect this error and the usual Undefined Behaviour rules apply (i.e., the standard doesn't define what will be printed). As it happens, GCC seems to show the same results as i7.1:

    $ gcc -std=c++14 -x c++ a.c b.c
    $ ./a.out
    foo called from main: return value = 3, address = 0x55cd03df5670
    foo called from g: return value = 3, address = 0x55cd03df5670