I wrote a tool to do this in 10 minutes just several days ago. It iterates over a file, and removes one #include at a time (while keeping all the other headers in place), and each time builds a file. For any successful build, it reports that header.<p>I used this to prune unnecessary includes throughout the TXR project.<p>There are some obvious false positives, like includes wrapped in #if&#x2F;#ifdef and also certain system headers which look unnecessary on one system, but are actually required on others (due to the mess that is called POSIX).<p>After I applied some of the header removals, I pushed the changes to git and went through all the supported platforms to validate the changes. Almost every platform had some problem which resulted in some header having to be put back: MinGW, Cygwin, Solaris, Mac OS X.  For instance, Mac OS is a stickler for needing &lt;signal.h&gt; to declare the kill() function. On other platforms, it also shows up elsewhere, perhaps unistd. On glibc, you get va_list if you include &lt;stdio.h&gt;. Some platforms guard against this; they use some internal typedef name instead of va_list for vfprintf, so you must include &lt;stdarg.h&gt; for va_list. Various problems of this type. So just because a tool tells you, &quot;hey this compiles without &lt;stdarg.h&gt; just fine&quot;, that of course means &quot;well, on this system&quot;.<p><pre><code>   #!&#x2F;usr&#x2F;local&#x2F;bin&#x2F;txr
   @(next :args)
   @file.c
   @(next `@file.c`)
   @(collect)
   @  (some)
   @lines
   @  (and)
   @    (line linenum)
   #include @header
   @  (end)
   @(end)
   @(require (boundp &#x27;linenum))
   @(do (rename-path `@file.c` `@file.c.bak`))
   @(try)
   @  (do (each ((rem-line linenum)
                 (rem-hdr header))
            (with-stream (s (open-file `@file.c` &quot;w&quot;))
              (tprint (partition* lines (pred rem-line)) s))
            (ignerr (remove-path `opt&#x2F;@file.o`))
            (when (zerop (sh `make opt&#x2F;@file.o &gt; &#x2F;dev&#x2F;null 2&gt;&amp;1`))
              (put-line `@file.c:@{rem-line}: can remove @{rem-hdr}`))))
   @(finally)
   @  (do (rename-path `@file.c.bak` `@file.c`))
   @(end)
</code></pre>
The output is in a form that looks like compiler diagnostics. I can stick it in errors.err and run &quot;vim -q&quot;.<p>This simple tool works in conjunction with the project rule that that headers don&#x27;t include other headers. The .c files include everything they need in the right order. But the inclusions are added by hand by copy and paste, which can pull in something that isn&#x27;t actually needed.<p>You have to iterate the tool. If you remove some header which isn&#x27;t needed, a second pass can then determine that yet another header isn&#x27;t needed, because it was only needed by that which was just removed.  The iteration isn&#x27;t worth building into the code.