Table of Contents list

ELF symbol visibility and binding can be complex, especially when considering the interaction of different visibility attributes, compiler options, and link types. This page provides an interactive summary of how these factors affect the binding and visibility of various types of symbols in C++, both in the object files and in the final linked binary. Generated using GCC 11.4.0 on Linux x86-64.

ELF symbol binding and visibility summary

Choose a link type, -fvisibility setting, and -fvisibility-inlines-hidden option to generate the table below. The rows summarize binding and visibility for each function type in the object file, the final target symbol table (.symtab), and whether the symbol appears in .dynsym. The meaning of the different fields can be found in the ELF specification in the references below.

Link type
-fvisibility
-fvisibility-inlines-hidden

Compile: g++
Link: g++
Description Definition Object Binding Object Visibility Binary Binding Binary Visibility .dynsym

Caveats

Methodology

Test program built with a matrix of different visibility settings and link types, then parsing the output of readelf to generate the table.

Test program source code ...

main.cpp

content_copy check
#include <cstdio>

void func() { std::puts("func"); }

static void static_func() { std::puts("static_func"); }

namespace {
void anonymous_func() { std::puts("anonymous_func"); }
} // namespace

inline void inline_func() { std::puts("inline_func"); }

template <class T>
void template_func() {
    std::puts("template_func<...>");
}

template <class T>
inline void inline_template_func() {
    std::puts("inline_template_func<...>");
}

template <class T>
struct Class {
    void func() { std::puts("Class<...>::func"); }
    static void static_func() { std::puts("Class<...>::static_func"); }
    void out_of_line_func();
};

template <class T>
void Class<T>::out_of_line_func() {
    std::puts("Class<...>::out_of_line_func");
}

struct NoInst;

struct InstDefault;
template struct [[gnu::visibility("default")]] Class<InstDefault>;

template <class T>
struct [[gnu::visibility("default")]] DefaultClass {
    void func() { std::puts("Class<...>::func"); }
    static void static_func() { std::puts("Class<...>::static_func"); }
    void out_of_line_func();
};

template <class T>
void DefaultClass<T>::out_of_line_func() {
    std::puts("Class<...>::out_of_line_func");
}

struct Inst;
template struct DefaultClass<Inst>;

struct InstProtected;
template struct [[gnu::visibility("protected")]] DefaultClass<InstProtected>;

[[gnu::visibility("default")]] void default_func() {
    std::puts("default_func");
}

[[gnu::visibility("protected")]] void protected_func() {
    std::puts("protected_func");
}

[[gnu::visibility("hidden")]] void hidden_func() { std::puts("hidden_func"); }

int main() {
    func();
    static_func();
    anonymous_func();
    inline_func();
    template_func<int>();
    inline_template_func<int>();
    Class<NoInst> obj;
    obj.func();
    obj.static_func();
    obj.out_of_line_func();
    Class<InstDefault> inst_default_obj;
    inst_default_obj.func();
    inst_default_obj.static_func();
    inst_default_obj.out_of_line_func();
    DefaultClass<NoInst> default_obj;
    default_obj.func();
    default_obj.static_func();
    default_obj.out_of_line_func();
    DefaultClass<Inst> default_inst_obj;
    default_inst_obj.func();
    default_inst_obj.static_func();
    default_inst_obj.out_of_line_func();
    DefaultClass<InstProtected> default_inst_protected_obj;
    default_inst_protected_obj.func();
    default_inst_protected_obj.static_func();
    default_inst_protected_obj.out_of_line_func();
    default_func();
    protected_func();
    hidden_func();
}

CMakeLists.txt

content_copy check
cmake_minimum_required(VERSION 3.24)
project(Visibility)
set(CMAKE_VERBOSE_MAKEFILE ON)
add_compile_options(-Wall -Wextra)

function(visibility_print_symbols tgt)
    string(JOIN "|" regex ${ARGN} "Symbol table" "Bind" "Vis")
    add_custom_command(TARGET ${tgt} POST_BUILD VERBATIM
        COMMAND ${CMAKE_COMMAND} -E echo
        COMMAND ${CMAKE_COMMAND} -E echo "Object file symbols ($<PATH:GET_FILENAME,$<TARGET_OBJECTS:${tgt}>>):"
        COMMAND ${CMAKE_READELF} -WCs "$<TARGET_OBJECTS:${tgt}>" | grep -E "${regex}"
        USES_TERMINAL)
    add_custom_command(TARGET ${tgt} POST_BUILD VERBATIM
        COMMAND ${CMAKE_COMMAND} -E echo
        COMMAND ${CMAKE_COMMAND} -E echo "Executable file symbols ($<PATH:GET_FILENAME,$<TARGET_FILE:${tgt}>>):"
        COMMAND ${CMAKE_READELF} -WCs "$<TARGET_FILE:${tgt}>" | grep -E "${regex}"
        USES_TERMINAL)
endfunction()

foreach(vis default hidden protected)
    foreach(vis-inl inl-hidden inl-default)
        set(vis-inl-value "Off")
        if (${vis-inl} STREQUAL "inl-hidden")
            set(vis-inl-value "On")
        endif()
        add_executable(${vis}-${vis-inl}-exe main.cpp)
        set_target_properties(${vis}-${vis-inl}-exe PROPERTIES CXX_VISIBILITY_PRESET ${vis})
        set_target_properties(${vis}-${vis-inl}-exe PROPERTIES VISIBILITY_INLINES_HIDDEN ${vis-inl-value})
        visibility_print_symbols(${vis}-${vis-inl}-exe "func")

        add_executable(${vis}-${vis-inl}-export-exe main.cpp)
        set_target_properties(${vis}-${vis-inl}-export-exe PROPERTIES CXX_VISIBILITY_PRESET ${vis})
        set_target_properties(${vis}-${vis-inl}-export-exe PROPERTIES VISIBILITY_INLINES_HIDDEN ${vis-inl-value})
        set_target_properties(${vis}-${vis-inl}-export-exe PROPERTIES ENABLE_EXPORTS ON)
        visibility_print_symbols(${vis}-${vis-inl}-export-exe "func")

        add_library(${vis}-${vis-inl}-shared SHARED main.cpp)
        set_target_properties(${vis}-${vis-inl}-shared PROPERTIES CXX_VISIBILITY_PRESET ${vis})
        set_target_properties(${vis}-${vis-inl}-shared PROPERTIES VISIBILITY_INLINES_HIDDEN ${vis-inl-value})
        visibility_print_symbols(${vis}-${vis-inl}-shared "func")
    endforeach()
endforeach()

External references