1

I am learning C++ and I am confused by inline behavior. On cppreference I found that " function included in multiple source files must be inline". Their example is the following:

// header file
#ifndef EXAMPLE_H
#define EXAMPLE_H
// function included in multiple source files must be inline
inline int sum(int a, int b) 
{
    return a + b;
}
#endif

// source file #2
#include "example.h"
int a()
{
    return sum(1, 2);
}

// source file #1
#include "example.h"
int b()
{
    return sum(3, 4);
}

This is a bit confusing to me - I thought that the ifndef guard was doing this job exactly, i.e. preventing problems when the same file was included multiple times. In any case, I wanted to test this, so I prepared the following:

// Sum.h
#ifndef SUM_H
#define SUM_H
int sum(int a, int b);
#endif

// Sum.cpp
int sum(int a, int b){
    return a + b;
}

// a.h
#ifndef A_H
#define A_H
int af();
#endif

// a.cpp
#include "sum.h"

int af(){
    return sum(3, 4);
}

// b.h
#ifndef B_H
#define B_H
int bf();
#endif 

// b.cpp
#include "sum.h"

int bf(){
    return sum(1, 2);
}

// main.cpp
#include "sum.h"
#include "a.h"
#include "b.h"
#include <iostream>
int main() {
    std::cout << af() + bf();
}

And this works fine as expected. Then I use define the sum function to be inline in sum.cpp and sum.h, and it fails to compile:

"sum(int, int)", referenced from:
      bf() in b.cpp.o
      af() in a.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Can somebody clarify this for me?

Ant
  • 5,151
  • 2
  • 26
  • 43
  • 3
    related/good read: https://stackoverflow.com/questions/249701/why-arent-my-compile-guards-preventing-multiple-definition-inclusions – NathanOliver Jul 24 '18 at 14:49
  • 5
    Include guards prevent including the same header more than once in one source file. They do not in any way affect the behavior of a header included in several separate source files. – Igor Tandetnik Jul 24 '18 at 14:49
  • 1
    Your example does not define a function in a header. If you want help with an error, show the code that's failing, not the code that's working. – Igor Tandetnik Jul 24 '18 at 14:50

3 Answers3

5

The include guard with #ifndef/#define/#endif or with #pragma once only prevents the inclusion for every single translation unit.

Lets assume you have a header like:

#pragma once

void Do() {}

and two *.cpp files which do the include. If you now compile with

g++ source1.cpp source2.cpp 

you will get a

multiple definition of Do() in xy line/file

Every source file will compiled "alone" and so the guard is not seen from the second translation unit which was set by the first one. Both translations ( compilations ) are done fully independent. As this, the include guard will not protect anything in this case.

For this it is possible to define a function definition as inline. Now both definitions will be presented to the linker but marked as "weak". The linker now did not complain about the two definitions and only takes one of them ( typically the last! ) which is not important, as both are the same in that case.

So the include guard has the sense, that you can include a file multiple times to one translation unit. This will happen typically only if you indirect include a header. Lets say a.h has the function definition and b.h and c.h each include a.h. If your cpp-file includes now b.h and c.h both of them include a.h. So you have, without include guard, multiple definitions. This is the use case for include guards.

And the opposite problem having a "undefined symbol" if the function is defined inline but not visible in all using translation units:

Example:

Having that file:

inline void sum() {}
void f1() { sum(); }

and compile with -O0 produces, output of nm f1.o|c++filt

0000000000000000 T f1()
0000000000000000 W sum()

We see the symbol for sum defined as weak, so it can be present multiple times in link stage. It will also be used if a second translation unit which did not "see" the definition, will be linked without problems.

But using "-O3" you get:

0000000000000000 T f1()

That is implementation specific to the compiler! A compiler can provide inlined functions. Typically they do not, if higher optimization levels are in use.

As a rule: If a function is defined inline, its definition must be visible to every translation unit before it is used there!

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • Your statement about linker is questionable, it may work that way on some platforms, but does not have to. Inline function may not produce any function to linker – Slava Jul 24 '18 at 15:05
  • @Slava Inline keyword has nothing todo with inlining a function during optimization! This was the case long time ago. In current standard the inline keyword allows to have multiple definitions of a symbol. If the optimizer already inlines the function and no symbol is present in the intermediate files, you will not get an error in link stage, independent of the inline keyword! – Klaus Jul 24 '18 at 15:08
  • Right, but inline function must be not visible in another translation unit not matter if it inlined by optimization or not. So your statement about linker "allowing" multiple copies is not quite correct. – Slava Jul 24 '18 at 15:11
  • @Slava: It has nothing to do with the visibility in another translation unit. The linker sees multiple definitions from different translation units. Sorry, I can't catch your point! – Klaus Jul 24 '18 at 15:13
  • And about optimization - for inline function (unlike regular) compiler is not required to add symbol to the linker optimized or not. – Slava Jul 24 '18 at 15:13
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176661/discussion-between-klaus-and-slava). – Klaus Jul 24 '18 at 15:14
  • Thank you, this is very helpful. You still don't address why it fails to compile when I add the inline keyword, but that point was addressed by @RSahu. I will accept this answer, but you can make it even more complete by addressing that point :) – Ant Jul 24 '18 at 19:13
  • @Ant: Thanks for your hint! I added an explanation to that case. Please take a review on it! Thanks! – Klaus Jul 25 '18 at 08:04
1

Then I use define the sum function to be inline in sum.cpp and sum.h, and it fails to compile:

When a function is declared inline, its definition must be available in all the translation units that use the function. Defining the function in only one translation unit is not correct. That's why you are seeing the linker errors.

Move the definition of the function to sum.h to resolve the linker errors.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

You should read documentation carefully, beside the statement:

function included in multiple source files must be inline

it also says this:

2) The definition of an inline function or variable (since C++17) must be present in the translation unit where it is accessed (not necessarily before the point of access).

which you violated in your example hense the error.

Slava
  • 43,454
  • 1
  • 47
  • 90