The other day, I was asked whether we should worry about the space taken up by unused functions in our codebase. My answer followed this rambling path:
Unused function templates won’t even get instantiated, so they really have no existence at link-time.
Unused inline functions may be codegenned, but if you have compiler optimizations turned on, you probably won’t see any codegen for inline functions unless they are actually called.
Unused static non-member functions, logically, should be codegenned, but since static functions aren’t visible beyond their own TU, the compiler may be able to detect and warn about them — and with compiler optimizations turned on, the compiler may decide that there’s no reason to generate code for them either.
Unused non-static (external-linkage) functions, though, must be codegenned, because the compiler cannot prove that no other TU calls them. An optimizing linker might be able to prove that nobody needs those definitions, and eliminate them — I personally have worked on a linker that did exactly that optimization — but in general your mental model of a linker should be that it won’t do that optimization.
The linker is likely able to prove that the function is unused; but without some degree of help from the compiler, it probably won’t be able to prove that it is safe to just snip that function’s bytes out of the text section. That might mess up some relative offset that the rest of the object file is secretly relying on.
“What if they’re marked private?”
Unused private (non-static) data members, logically, must take up space in the memory footprint of the class, so that every TU will agree on the
sizeofthe class. However, if in some specific TU the compiler can see the complete definition of every member and every friend of the class, then it can prove that the private member really is unused, and then it can give a warning. Clang does this.
Hmm, but Clang does this only for private data members, not for private member functions! That’s interesting!
Here’s the complete list of non-temploid non-inline entities you might encounter, and what the major compilers do with them if they’re provably unused. (Godbolt.)
Options-wise, I tested
-W1 -W2 -W3 -W4 -Od -O1 -O2 on MSVC,
-Wall -Wextra -O0 -O1 -O2 -O3 on the other three.
|Do we warn on an unused…||Clang||GCC||ICC||MSVC|
|private data member||-Wall|
|private static data member|
|private member function|
|private static member function|
|data member of private class|
|static data member of private class|
|member function of private class|
|static member function of private class|
|data member of anonymous-namespaced class|
|static data member of anonymous-namespaced class||-Wall||-Wall|
|member function of anonymous-namespaced class||-Wall|
|static member function of anonymous-namespaced class||-Wall|
|function taking anonymous-namespaced class||-Wall||-Wall|
|Do we optimize out an unused…||Clang||GCC||ICC||MSVC|
|private data member||—||—||—||—|
|private static data member||—||—||—||—|
|private member function||—||—||—||—|
|private static member function||—||—||—||—|
|static data member of private class||—||—||—||—|
|member function of private class||—||—||—||—|
|static member function of private class||—||—||—||—|
|static data member of anonymous-namespaced class||-O0||-O0||-O1|
|member function of anonymous-namespaced class||-O0||-O1||-O1|
|static member function of anonymous-namespaced class||-O0||-O1||-O1|
|function taking anonymous-namespaced class||-O0||-O1||-O1|
I claim that there’s a fair bit of room for improvement here!
Or am I missing some subtle mechanism by which “unused” private members might actually be referenced from other TUs?
UPDATE: Yes, I am! See my followup:
I have placed “–” in table cells where this loophole makes the suggested optimization technically impossible for a conforming compiler. All compilers’ diagnostics could use improvement, but the new optimization table shows that everyone (except MSVC) is doing the best they can, optimization-wise.