It actually depends on you compiler how it treats intra-compilation-unit calls, but there are a few recommendations.
For the first thing, if you're optimizing, the compiler may inline your functions even if they are not marked inline
. Actually it will strive to do so if the function is marked with the always_inline
attribute. As a result there will be no evidence that a function f calls a function g even if it actually does so. Note that if g is itself an externally reachable function, the compiler may generate its code twice (or more), first under its own name for calls from the outside, and then inlined into the object code of f (and other calling functions).
So, avoid optimization and somehow suppress always_inline
. You can even explicitly specify -fno-inline
to prevent inlining.
Second, your target architecture may have relative call and branch instructions. The compiler may take advantage of that if your f and g are placed into a code section common to them. It is the default for non-inline functions. In such a case, the compiler knows the offset between a place of call and the beginning of the callee at compilation time and can generate a relative call or jump instruction; no further relocation is required. Some compilers may emit a 'no-op' relocation, but some won't. No relocation means that the symbol is not referenced.
So, use -ffunction-sections
(and for data -fdata-sections
). Every function is then placed into its own section and the compiler will have no other choice but to generate a relocation for linker to fix up (thus making the callee's symbol referenced).
Note that if you use -ffunction-sections
and then specify --gc-sections
when calling ld
, the compiler will discard all unreferenced sections. If you then add -M
you will get the resulting module map. Discarded functions will not show up in the map.
As a side note, remember that there are also cases where static analysis is incapable of detecting that a function can never be called. For example, in a well-written C++ program __cxa_pure_virtual
will never be called, but nevertheless there will be references to it in virtual function tables of all abstract classes. Moreover, overrides of a plain virtual function will be referred through a virtual function table and linked even if there is no single call of that virtual function in the whole program. Symbolic analysis is unable detect these cases.