You would be hard pressed to find out why the default is "export everything". The language and its compilers have both evolved dramatically since its inception in the 1970s, where there are no release notes nor "working group" discussions available on the internet. "Structured programming" and goto
statements were of the time; very few people were thinking about using encapsulation to minimise the shared-state complexity problem. Fortran also made functions publicly visible.
I would surmise that as the language grew in popularity, ever larger systems emerged which may have broken early editions of the linker. So some means of circumventing this needed to be introduced. For some crazy reason they chose to use static
to hide functions from the linker to reduce its load (for me this is a bigger mystery as opposed to why linkage is arbitrarily public).
Practically, when declaring functions static
, aside from denying other modules access to "internals", it's worth hiding symbols from the linker in very large programs in order to to speed up the build time and reduce memory consumption. This can become unwieldy very quickly. Instead of sprinkling the codebase with static
to conceal methods, it actually makes more sense to use a compiler option to set hidden visibility as the default, then decorate the functions you do want to be visible to other modules.
In Linux you can direct the compiler to make hidden visibility the default (-fvisibility=hidden
):
see https://stackoverflow.com/a/52742992/1607937
In truth it's a little bit more complicated than that; there are other options that provide finer tuning of visibility. From https://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Function-Attributes.html:
visibility ("visibility_type")
The visibility attribute on ELF targets causes the declaration to be emitted with default, hidden, protected or internal visibility.
void __attribute__ ((visibility ("protected")))
f () { /* Do something. */; }
int i __attribute__ ((visibility ("hidden")));
See the ELF gABI for complete details, but the short story is:
default
Default visibility is the normal case for ELF.
This value is available for the visibility attribute to override
other options that may change the assumed visibility of symbols.
hidden
Hidden visibility indicates that the symbol will not be placed into
the dynamic symbol table, so no other module (executable or shared
library) can reference it directly.
internal
Internal visibility is like hidden visibility, but with additional
processor specific semantics. Unless otherwise specified by the
psABI, GCC defines internal visibility to mean that the function
is never called from another module. Note that hidden symbols,
while they cannot be referenced directly by other modules,
can be referenced indirectly via function pointers.
By indicating that a symbol cannot be called from outside
the module, GCC may for instance omit the load of a PIC
register since it is known that the calling function loaded
the correct value.
protected
Protected visibility indicates that the symbol will be placed in the
dynamic symbol table, but that references within the defining module
will bind to the local symbol. That is, the symbol cannot be
overridden by another module.
Not all ELF targets support this attribute.
(also see Peter Cordes' comment in the thread)
Also note that functions can be overridden by "bolt-on" implementations that can be linked in. This is useful for mocking methods in unit tests. It's worth using the "weak linkage" attribute if you intend to use this.
It's worth mentioning that in C++ it is preferred to use anonymous namespaces instead of static
to declare symbols as being "private":
namespace {
<module-private code>
} // anonymous namespace
See core guidelines SF.22 - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rs-unnamed2
In my experience, many companies embrace this in their coding standards.
Note that is not exactly equivalent to "static":
static and anonymous namespace are not the same thing. A function defined in an anonymous namespace will have external linkage. But it is guaranteed to live in a uniquely named scope. Indeed we can't refer to it outside of the translation unit it is defined in because it is unnamed.
... so for very large C++ programs it's still worth using -fvisibility=hidden
and decorate the methods you do want to be visible to the linker, even with using anonymous namespaces.