Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences of the inline-keyword in C and C++

Consider the following C++-code with an inline-function:

// this function is in a header-file:
// recursion prevents inlining
inline int calc(int k){
    if(k<=1) return 1;
    return calc(k-1)+calc(k-2);
}

// this function is in a source-file:
int fun(int k){
    return calc(k);
}

Here I use recursion, to simulate the case when compiler was not able to inline the function calc.

The resulting assembly (compiled with -Os, see live on https://godbolt.org/):

...
        .weak   calc(int)
        .type   calc(int), @function
calc(int):
    // some assembler


       .text
        .globl  fun(int)
        .type   fun(int), @function
fun(int):
...
        jmp     calc(int)

As expected, the compiler was not able to inline calc, and thus emitted code for it, but due to inline-keyword it becomes a weak symbol.

Compiling the same code as C, produces a different result (with -Os,see live on https://godbolt.org/):

.Ltext0:
        .globl  fun
        .type   fun, @function
fun:
...
        jmp     calc

The most notable difference: there is no code emitted for calc, so basically the linker will not able to link the executable, as the definition of calc is missing.

Obviously, inline means different things in C compared to C++.

What are differences for inline in C compared to C++? How the definition of calc should be done, that it can be used in a header in C and C++ at the same time?

like image 976
ead Avatar asked Jun 16 '21 14:06

ead


Video Answer


1 Answers

While the inline-keyword has the same goal in C and C++: to provide the definition of a function at compile time in multiple translation units, these languages use different methods to cope with its fallout - multiple definitions of the same symbol in different translation units.

When the C- or C++-compiler really inlines the inline-function in a translation unit, its definition is no longer needed and no corresponding symbol is emitted. As for example here:

inline int fun(){return 2;}

int doit(){
    return fun();
} 

the resulting assembler is (live)

doit():
        movl    $2, %eax
        ret

However, when the function don't get inlined, the C++-standard mandates among other things the following:

  1. There may be more than one definition of an inline function in the program as long as each definition appears in a different translation unit and all definitions are identical.
  2. The function has the same address in every translation unit.

This more or less describes a weak symbol - and no surprise, C++-compiler emits a weak symbol in the OP's code - it has to, because the C++-standard doesn't guarantee, that this symbol will be defined in another translation unit.

On the other hand, the C-standard gurantees, that the definition of the inline-function must be provided in another translation unit(i.e. has external linkage) and thus (in order to avoid multiple definition of the same symbol) nothing is emited for the inline function - it is just called.

Thus, there must be a c-file, in which the function is "instantiated" via

inline int calc(int k)

This doesn't work well with the idea of having a header-only library. An alternative is, the use internal linkage via static-keyword, i.e.

static inline int calc(int k){
    if(k<=1) return 1;
    return calc(k-1)+calc(k-2);
}

This will emit a local symbol in every translation unit (if not inlined), independent of whether compiled as C or C++. That means, there are multipe definitions (i.e. in every translation object the function has a different address) of the same function, which will possible lead to a bigger executable, and is a setback for compared to C++-default behavior.

An alternative is to use the following definiton of inline:

#ifdef __cplusplus
    #define INLINE inline
#else
    #define INLINE static inline
#endif

and use as

INLINE int calc(int k){
    ...
}
like image 182
ead Avatar answered Sep 30 '22 17:09

ead