Skip to content

Instantly share code, notes, and snippets.

@htfy96
Last active September 23, 2024 18:13
Show Gist options
  • Save htfy96/50308afc11678d2e3766a36aa60d5f75 to your computer and use it in GitHub Desktop.
Save htfy96/50308afc11678d2e3766a36aa60d5f75 to your computer and use it in GitHub Desktop.
static inline vs inline vs static in C++

In this article we compared different behavior of static, inline and static inline free functions in compiled binary. All the following test was done under g++ 7.1.1 on Linux amd64, ELF64.

Test sources

header.hpp

#pragma once

inline int only_inline() { return 42; }
static int only_static() { return 42; }
static inline int static_inline() { return 42; }

a.cpp

#include "header.hpp"

int a()
{
    return static_inline() + only_inline() + only_static();
}

b.cpp

#include "header.hpp"

int a();

int b()
{
    return static_inline() + only_inline() + only_static();
}

int main()
{
    return a() + b();
}

Compiled binaries

a.o

readelf -sW a.o | c++filt -t 
# -s: symbol table
# -W: display in wide format
Symbol table '.symtab' contains 14 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000    11 FUNC    LOCAL  DEFAULT    2 only_static() # local
     7: 000000000000000b    11 FUNC    LOCAL  DEFAULT    2 static_inline() # local
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    9 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
    11: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
    12: 0000000000000000    11 FUNC    WEAK   DEFAULT    6 only_inline() # weak
    13: 0000000000000016    37 FUNC    GLOBAL DEFAULT    2 a()

b.o

Symbol table '.symtab' contains 17 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS b.cpp
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
     6: 0000000000000000    11 FUNC    LOCAL  DEFAULT    2 only_static() # LOCAL
     7: 000000000000000b    11 FUNC    LOCAL  DEFAULT    2 static_inline() # LOCAL
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    9 
    10: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
    11: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
    12: 0000000000000000    11 FUNC    WEAK   DEFAULT    6 only_inline() # WEAK
    13: 0000000000000016    37 FUNC    GLOBAL DEFAULT    2 b()
    14: 000000000000003b    30 FUNC    GLOBAL DEFAULT    2 main
    15: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND a()

a.out

As in compiled executable, every copies of symbol marked LOCAL will be preserved, while only one instance of WEAK symbols will only be preserved.

Symbol table '.symtab' contains 71 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000238     0 SECTION LOCAL  DEFAULT    1 
     2: 0000000000000254     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000000274     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000000298     0 SECTION LOCAL  DEFAULT    4 
     5: c0     0 SECTION LOCAL  DEFAULT    5 
     6: 0000000000000368     0 SECTION LOCAL  DEFAULT    6 
     7: 0000000000000412     0 SECTION LOCAL  DEFAULT    7 
     8: 0000000000000420     0 SECTION LOCAL  DEFAULT    8 
     9: 0000000000000440     0 SECTION LOCAL  DEFAULT    9 
    10: 0000000000000518     0 SECTION LOCAL  DEFAULT   10 
    11: 0000000000000530     0 SECTION LOCAL  DEFAULT   11 
    12: 0000000000000540     0 SECTION LOCAL  DEFAULT   12 
    13: 0000000000000550     0 SECTION LOCAL  DEFAULT   13 
    14: 0000000000000774     0 SECTION LOCAL  DEFAULT   14 
    15: 0000000000000780     0 SECTION LOCAL  DEFAULT   15 
    16: 0000000000000784     0 SECTION LOCAL  DEFAULT   16 
    17: 00000000000007f8     0 SECTION LOCAL  DEFAULT   17 
    18: 0000000000200de0     0 SECTION LOCAL  DEFAULT   18 
    19: 0000000000200de8     0 SECTION LOCAL  DEFAULT   19 
    20: 0000000000200df0     0 SECTION LOCAL  DEFAULT   20 
    21: 0000000000200fd0     0 SECTION LOCAL  DEFAULT   21 
    22: 0000000000201000     0 SECTION LOCAL  DEFAULT   22 
    23: 0000000000201018     0 SECTION LOCAL  DEFAULT   23 
    24: 0000000000201028     0 SECTION LOCAL  DEFAULT   24 
    25: 0000000000000000     0 SECTION LOCAL  DEFAULT   25 
    26: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS init.c
    27: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    28: 0000000000000580     0 FUNC    LOCAL  DEFAULT   13 deregister_tm_clones
    29: 00000000000005c0     0 FUNC    LOCAL  DEFAULT   13 register_tm_clones
    30: 0000000000000610     0 FUNC    LOCAL  DEFAULT   13 __do_global_dtors_aux
    31: 0000000000201028     1 OBJECT  LOCAL  DEFAULT   24 completed.6991
    32: 0000000000200de8     0 OBJECT  LOCAL  DEFAULT   19 __do_global_dtors_aux_fin
    33: 0000000000000650     0 FUNC    LOCAL  DEFAULT   13 frame_dummy
    34: 0000000000200de0     0 OBJECT  LOCAL  DEFAULT   18 __frame_dummy_init_array_
    35: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS a.cpp
    36: 000000000000065a    11 FUNC    LOCAL  DEFAULT   13 only_static() # COPY 1
    37: 0000000000000665    11 FUNC    LOCAL  DEFAULT   13 static_inline() # COPY 1
    38: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS b.cpp
    39: 00000000000006a0    11 FUNC    LOCAL  DEFAULT   13 only_static() # COPY 2
    40: 00000000000006ab    11 FUNC    LOCAL  DEFAULT   13 static_inline() # COPY 2
    41: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS crtstuff.c
    42: 00000000000009e0     0 OBJECT  LOCAL  DEFAULT   17 __FRAME_END__
    43: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS 
    44: 0000000000000784     0 NOTYPE  LOCAL  DEFAULT   16 __GNU_EH_FRAME_HDR
    45: 0000000000201000     0 OBJECT  LOCAL  DEFAULT   22 _GLOBAL_OFFSET_TABLE_
    46: 0000000000200de8     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_end
    47: 0000000000200de0     0 NOTYPE  LOCAL  DEFAULT   18 __init_array_start
    48: 0000000000200df0     0 OBJECT  LOCAL  DEFAULT   20 _DYNAMIC
    49: 0000000000201018     0 NOTYPE  WEAK   DEFAULT   23 data_start
    50: 0000000000000770     2 FUNC    GLOBAL DEFAULT   13 __libc_csu_fini
    51: 0000000000000550    43 FUNC    GLOBAL DEFAULT   13 _start
    52: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
    53: 0000000000000670    37 FUNC    GLOBAL DEFAULT   13 a()
    54: 0000000000000774     0 FUNC    GLOBAL DEFAULT   14 _fini
    55: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    56: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
    57: 0000000000000780     4 OBJECT  GLOBAL DEFAULT   15 _IO_stdin_used
    58: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
    59: 0000000000201018     0 NOTYPE  GLOBAL DEFAULT   23 __data_start
    60: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@@GLIBC_2.2
    61: 0000000000201028     0 OBJECT  GLOBAL HIDDEN    23 __TMC_END__
    62: 0000000000201020     0 OBJECT  GLOBAL HIDDEN    23 __dso_handle
    63: 0000000000000700   101 FUNC    GLOBAL DEFAULT   13 __libc_csu_init
    64: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT   24 __bss_start
    65: 0000000000000695    11 FUNC    WEAK   DEFAULT   13 only_inline() # only one
    66: 0000000000201030     0 NOTYPE  GLOBAL DEFAULT   24 _end
    67: 00000000000006b6    37 FUNC    GLOBAL DEFAULT   13 b()
    68: 0000000000201028     0 NOTYPE  GLOBAL DEFAULT   23 _edata
    69: 00000000000006db    30 FUNC    GLOBAL DEFAULT   13 main
    70: 0000000000000518     0 FUNC    GLOBAL DEFAULT   10 _init

Conclusion

  • inline marks the symbol WEAK, which hints linker to choose arbitary one of definition in object files
  • static marks the symbol LOCAL, which restricts the symbol in current translation unit, and the linker may keep multiple instances of (possibly different) definition.
  • static inline generally works as static, but the inline keyword suggest compiler trying to inline this function.

Extra credits

In C++11 it is recommended to use function in anonymous namespace over static functions.

What if we add the following code into header.hpp:

namespace {
    int anon_namespace() { return 42; }
}

a.o

     8: 0000000000000016    11 FUNC    LOCAL  DEFAULT    2 _ZN12_GLOBAL__N_114anon_namespaceEv # (anonymous namespace)::anon_namespace()

b.o

     8: 0000000000000016    11 FUNC    LOCAL  DEFAULT    2 _ZN12_GLOBAL__N_114anon_namespaceEv # (anonymous namespace)::anon_namespace()

a.out

    38: 0000000000000670    11 FUNC    LOCAL  DEFAULT   13 (anonymous namespace)::anon_namespace()
    42: 00000000000006c8    11 FUNC    LOCAL  DEFAULT   13 (anonymous namespace)::anon_namespace()

You may notice that this is exactly the same as static, except for its mangled name. So why the commitee introduced anonymous namespace? Read https://stackoverflow.com/questions/4977252/why-an-unnamed-namespace-is-a-superior-alternative-to-static for explanation.

Read more

@joeri-hu
Copy link

joeri-hu commented Dec 2, 2021

Please note that the C++ Core Guidelines definitely does NOT recommend to put anonymous namespaces in header files. Referring to rule SF.21 specifically. The common convention is to use a named namespace like detail, internal, or something along those lines, instead.

@kkHAIKE
Copy link

kkHAIKE commented Jan 23, 2023

wath about template function case

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment