Visibility
Pieter PELF 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.
Compile:
g++Link:
g++
| Description | Definition | Object Binding | Object Visibility | Binary Binding | Binary Visibility | .dynsym |
|---|
Caveats
-
The visibility attribute of an explicit instantiation of a class
template does not override the visibility of the class definition.
No warning is emitted by GCC; the attribute is simply ignored.
However, if the class template definition is marked with default visibility,
but the explicit instantiation does not specify a visibility,
the visibility of the explicit instantiation is determined by the
-fvisibilityoption.
Contrary to the implicit instatiation case, such an explicit instantiation without a visibility attribute does preserve default visibility if-fvisibility=defaultand-fvisibility-inlines-hiddenare enabled. -
If an
extern templatedeclaration for an explicit class template instantiation exists, the visibility of theextern templatedeclaration is used, even if the explicit instantiation does not specify a visibility. GCC warns if the visibility of theextern templatedeclaration does not match the visibility of the explicit instantiation (warning: type attributes ignored after type is already defined [-Wattributes]). -
In the case of an implicit instantiation,
the
-fvisibility=hiddenand-fvisibility-inlines-hiddenoptions hide member functions of class templates, even if the class template itself is marked with default visibility. The-fvisibility-inlines-hiddenoption only hides inline member functions of class templates, without affecting member functions with out-of-line definitions.
To force visibility of all member functions, the class template must be explicitly instantiated with default visibility. This should be done before any implicit instantiations occur, which typically means it must be done in the header file, using anextern template. -
Even though function templates and inline function templates both
emit weak symbols (similar to ordinary inline functions),
the visibility of the emitted symbols is different when
-fvisibility-inlines-hiddenis enabled.
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
#include <cstdio>void func() { std::puts("func"); }static void static_func() { std::puts("static_func"); }namespace {void anonymous_func() { std::puts("anonymous_func"); }} // namespaceinline 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
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 VERBATIMCOMMAND ${CMAKE_COMMAND} -E echoCOMMAND ${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 VERBATIMCOMMAND ${CMAKE_COMMAND} -E echoCOMMAND ${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()