I\'m cleaning up some C code. There are global variables everywhere, but not all of them are used. I want to clean up those. But it\'s too much work to test them one by one.
You can generate a list of all global variables in your directory using the very helpful ctags(1)
command with the -x
command line option:
ctags -x --c-kinds=v --file-scope=no *.c
This can be combined with the also-helpful gid(1)
command (assuming you've run mkid(1)
on your sources first):
for name in `ctags -x --c-kinds=v --file-scope=no *.c | awk '{print $1;}' | sort -u` ; do gid -R filenames $name ; done
This gives you a helpful list of which files use which globals:
$ for name in `ctags -x --c-kinds=v --file-scope=no *.c | awk '{print $1;}' | sort -u` ; do gid -R filenames $name ; done
basedir parser.h ./{parser_include,parser_main}.c
binary_input parser_main.c
cache_fd parser.h ./{parser_interface,parser_main}.c
conf_quiet parser.h parser_main.c
conf_verbose parser.h ./{parser_interface,parser_main}.c
...
It isn't perfect (as Ira points out), but it should be a good start.
If they are only used within the file, you can declare them "static" and GCC will warn if they are never used.
If they are used from multiple files... I'm not sure how you could detect that, other than grepping or using an IDE, because they will show up as linker symbols and could in theory be accessed by any code that links with your code...
This answer applies to the original question. The question has subsequently been changed.
It is impossible in principle to determine if a global variable is used or needed. Constructors can have side-effects, so even if a global is never accessed, it may still be needed.
True story: Programmer who shall remain nameless removed an 'unused' global from a production application. Unfortunately, the constructor for that global allocated memory from an allocator that initializes on first allocation.
As a result of him removing the unused global, the next object created using that allocator caused the allocator to initialize. Unfortunately, the initialization wasn't thread-safe (and was clearly documented as such) -- the purpose of the global was to ensure it initialized before any threads were created.
Let's just say there were very bad consequences (involving this company's largest customer -- a well-known three-letter government agency) and leave it at that.
A human being must make the determination that a global does not need to be created just because it is unused, and that can be a remarkably complicated decision to make.
Easy? No. For a global variable X, you need to scan every compilation unit in your code for a potential access to X (a read, a write, or the generation of a reference). If there are no such accesses, then you can be reasonably sure (you don't have any assembly code, right?) that X isn't used.
It may even be the case that X is referenced in one of the above ways, but in fact has no actual effect on the program (e.g., X is read but ignore, written but not read, address taken but never dereferenced). That makes X effectively dead. Determining this requires global data flow analysis.
(@ChrisLutz posted this idea as a comment, but I thought of it before I read the comment so I'm going to post it anyway. I'll try to add some value by expanding on it.)
In a separate copy of your source code, comment out the declarations of all your global variables and recompile everything. Compare the list of variables you commented out to the ones mentioned in compiler error messages. Anything not mentioned in an error message is unused.
Note that your build process might not try to build everything of some compilations fail, so that could give you some incomplete results.
A slightly different approach that avoids this problem is an iterative one: comment out all your globals, try to build, then uncomment the declarations for anything that the compiler complained about. Iterate until you get a clean build, and see what's still commented out.
Doxygen may be able to give you a list of uses for global variables.