extern "C" inline functions
Asked Answered
H

1

8

Will this code result in undefined behavior?

header.h

#ifdef __cplusplus
extern "C"
{
#endif

inline int foo(int a)
{
    return a * 2;
}

#ifdef __cplusplus
}
#endif

def.c

#include "header.h"

extern inline int foo(int a);

use.c

#include "header.h"

int bar(int a)
{
    return foo(a + 3);
}

main.cpp

#include <stdio.h>
#include "header.h"

extern "C"
{
    int bar(int a);
}

int main(int argc, char** argv)
{
    printf("%d\n", foo(argc));
    printf("%d\n", bar(argc));
}

This is a example of a program where an inline function has to be used in both C and C++. Would it work if def.c was removed and foo was not used in C? (This is assuming that the C compiler is C99.)

This code works when compiled with:

gcc -std=c99 -pedantic -Wall -Wextra -c -o def.o def.c
g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c
g++ -std=c++11 -pedantic -Wall -Wextra -o extern_C_inline def.o main.o use.o

foo is only in extern_C_inline once because the different versions that the compiler outputs in different object files get merged, but I would like to know if this behavior is specified by the standard. If I remove extern definition of foo and make it static then foo will appear in the extern_C_inline multiple times because the compiler outputs it in each compilation unit.

Hostile answered 28/10, 2014 at 23:21 Comment(20)
Does C even recognize an inline keyword? I thought they spell it differently.Catechize
@BenVoigt: Yes, it has slightly different semantics though.Whitehorse
I don't know why I thought the C version had some underscores in it.Catechize
@BenVoigt The version with underscores was a popular extension from before inline was standardised.Einberger
@jxh In C you need a extern inline definition somewhere to make sure a non-inline version is in the object file. See https://mcmap.net/q/16705/-what-does-extern-inline-doHostile
@jxh Function bar is just some function that uses foo. It does not matter what bar does other than that. It is only there so that foo is used in both C and C++.Hostile
What will break if you make your inline function static?Autointoxication
@Autointoxication If I make foo static then it will appear in the executable multiple times which will increase the size of the executable.Hostile
Which compiler are you using? The most commonly used ones will not create code in the object file for an unused static line function.Autointoxication
@Autointoxication I'm using gcc, but without optimization so the compiler does not actually inline foo. This is to emulate the case where foo is called by function pointer and so the compiler can't inline it.Hostile
@jxh: My question is about weather this relies on the specific compiler or if the behaviour is specified by the standard. I'm sorry I did not make this clear.Hostile
@Autointoxication "make it static" is generally not a good solution, it has other consequences and changes the semantics of the program (e.g. if there is a static local variable in foo() then making foo static means you get a different static local in every translation unit, not a single one for the whole program). Use static where it makes sense, not just to solve linker errors.Billyebilobate
@JonathanWakely: Yes, a static inline function is much more like a macro, which is what I like about it.Autointoxication
funny, that's exactly what I don't like about it. macros suck.Billyebilobate
@JonathanWakely: Yeah, that's a popular thing to say, and yet every C programmer uses them.Autointoxication
@Autointoxication They may be like macros, but if you take a function pointer of a static inline function, then the compiler won't inline it, and it will create a new version in each translation unit which will increase the size of the object file.Hostile
I would typically avoid passing pointers to globally inlined functions. Seeing code that did so would probably indicate a need for a design change.Autointoxication
@jxh: What if your writing a library, and you don't have control over the code that uses the inline function by pointer.Hostile
Just don't give the library a pointer to an inline function.Autointoxication
I meant that you don't have control over the code that uses the function by pointer, your just writing the library.Hostile
B
8

The program is valid as written, but def.c is required to ensure the code always works with all compilers and any combination of optimisation levels for the different files.

Because there is a declaration with extern on it, def.c provides an external definition of the function foo(), which you can confirm with nm:

$ nm def.o
0000000000000000 T foo

That definition will always be present in def.o no matter how that file is compiled.

In use.c there is an inline definition of foo(), but according to 6.7.4 in the C standard it is unspecified whether the call to foo() uses that inline definition or uses an external definition (in practice whether it uses the inline definition depends on whether the file is optimised or not). If the compiler chooses to use the inline definition it will work. If it chooses not to use the inline definition (e.g. because it is compiled without optimisations) then you need an external definition in some other file.

Without optimisation use.o has an undefined reference:

$ gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c
$ nm use.o
0000000000000000 T bar
                 U foo

But with optimisation it doesn't:

$ gcc -std=c99 -pedantic -Wall -Wextra -c -o use.o use.c -O3
$ nm use.o
0000000000000000 T bar

In main.cpp there will be a definition of foo() but it will typically generate a weak symbol, so it might not be kept by the linker if another definition is found in another object. If the weak symbol exists, it can satisfy any possible reference in use.o that requires an external definition, but if the compiler inlines foo() in main.o then it might not emit any definition of foo() in main.o, and so the definition in def.o would still be needed to satisfy use.o

Without optimisation main.o contains a weak symbol:

$ g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp
$ nm main.o
                 U bar
0000000000000000 W foo
0000000000000000 T main
                 U printf

However compiling main.cpp with -O3 inlines the call to foo and the compiler does not emit any symbol for it:

$ g++ -std=c++11 -pedantic -Wall -Wextra -c -o main.o main.cpp -O3
$ nm main.o
                 U bar
0000000000000000 T main
                 U printf

So if foo() is not inlined in use.o but is inlined in main.o then you need the external definition in def.o

Would it work if def.c was removed and foo was not used in C?

Yes. If foo is only used in the C++ file then you do not need the external definition of foo in def.o because main.o either contains its own (weak) definition or will inline the function. The definition in foo.o is only needed to satisfy non-inlined calls to foo from other C code.


Aside: the C++ compiler is allowed to skip generating any symbol for foo when optimising main.o because the C++ standard says that a function declared inline in one translation unit must be declared inline in all translation units, and to call a function declared inline the definition must be available in the same file as the call. That means the compiler knows that if some other file wants to call foo() then that other file must contain the definition of foo(), and so when that other file is compiled the compiler will be able to generate another weak symbol definition of the function (or inline it) as needed. So there is no need to output foo in main.o if all the calls in main.o have been inlined.

These are differnet semantics from C, where the inline definition in use.c might be ignored by the compiler, and the external definition in def.o must exist even if nothing in def.c calls it.

Billyebilobate answered 29/10, 2014 at 21:1 Comment(5)
Why it's not enough to answer that "there is no C compiler involved in this" since the point is C linkage, not C compiler, and even the code written in an extern "C" gets compiled by a C++ compiler ?Hibben
@user2485710, because that's not true. def.c and use.c are compiled by a C compiler.Billyebilobate
@JonathanWakely sure, but in a question that starts with extern "C", which a C++ only construct, how many times this thing will be compiled with a plain C compiler ? I think that the question is influenced by the fact that OP is assuming that even when using that code in a C++ code base it gets compiled by a C compiler .Hibben
@Hibben what do you think the #ifdef __cplusplus check is for? I think you are confused. The OP is clearly compiling def.c and use.c with a C compiler because that is shown in the question, see the gcc -std=c99 commands at the end.Billyebilobate
Thanks for the great answer! This definitely answers my question.Hostile

© 2022 - 2025 — McMap. All rights reserved.