Reputation: 15436
I would like to detect unused functions in my code by using the combination of -ffunctions-sections
compiler options, and the --gc-sections,--print-gc-sections
However, it shows false positive. Here is a simple reproducer :
mylib.c :
int plusone(int a)
{
return a + 1;
}
int myadd(int a, int b)
{
int c = plusone(a);
return c -1 +b;
}
main.c
#include <stdio.h>
#include "mylib.h"
int main(int argc, char*argv[])
{
int a = 1;
int b=3;
printf("%d\n", myadd(a,b));
return 0;
}
Compiling and linking script :
#!/bin/sh
gcc -c -o main.o -O2 -ffunction-sections -fdata-sections main.c
gcc -c -o mylib.o -O2 -ffunction-sections -fdata-sections mylib.c
gcc -o test -Wl,--gc-sections,--print-gc-sections main.o mylib.o
And here is the output :
/usr/bin/ld: removing unused section '.rodata.cst4' in file '/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o'
/usr/bin/ld: removing unused section '.data' in file '/usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o'
/usr/bin/ld: removing unused section '.text.plusone' in file 'mylib.o'
From the code of mylib.c, we can see that the plusone function is NOT unused, yet the --print-gc-sections message tells the opposite.
How does it work ? Is GCC inlining the function when used inside mylib.c, AND keeping a non inlined copy if it is called outside of mylib.c ?
How to make this technique more useful for the goal of detecting unused code ?
Upvotes: 0
Views: 231
Reputation: 155516
Is GCC inlining the function when used inside mylib.c, AND keeping a non inlined copy if it is called outside of mylib.c?
Yep. If you look at the disassembly of mylib.o
, myadd
does not call plusone
(both of them are just a single lea
computing the result, followed by a ret
). The cost of making that function call is greater than the work done inside it (adding 1
is literally one instruction on most architectures); setting up a call stack and calling the function is more expensive than that (on x86-64, the call
instruction alone is two bytes longer than the lea
it would factor out, ignoring handling argument setup and the like), so no matter how many times the function is called, it would be more efficient to inline it than centralize implementation. But it can't actually remove the implementation since it might be called from another translation unit in a later compilation phase, so it keeps the otherwise unused definition, just in case.
How to make this technique more useful for the goal of detecting unused code?
That's not really the job of the compiler. You could declare plusone
as static
, which means it doesn't get emitted as a separate function when it's universally inlined (I've verified this works on gcc 11.2; without static
I get your demonstrated output, with static
it doesn't claim to be trimming .text.plusone
), but this assumes nothing outside that translation unit needs it. In a sense, the compiler is detecting unused code; you declared a function for use outside the translation unit and never used it outside the translation unit, so it's "unused" and could have its scope limited (with static
) to avoid advertised-but-unused code.
If you're really paranoid about unused code, good static
discipline is a good idea anyway; while in this case inlining made sense no matter what, for more complex functions the compiler might be more hesitant to inline non-static
functions and the function might also be used many times outside the translation unit (so inlining might bloat the code in the translation unit without saving any space, possibly ruining processor caches by not sharing the same hot code). If it's static
, the compiler doesn't need to worry, and can more reliably determine if inlining makes sense or not.
Upvotes: 2