Search code examples
cgccopenglinclude

Compilation fails with #include "..." but not with #include <...>


I'm currently toying around with the C library NanoVG library. The library depends on OpenGL fucntions and has 2 header files nanovg.h and nanovg_gl.h. The latter file contains part of the implementation. For convenience, I have placed these two header files in /usr/include/nanovg.

When I try to compile the following code to an object file, gcc does not complain:

// working.c
#include <GL/gl.h>
#include <nanovg/nanovg.h>
#define NANOVG_GL3_IMPLEMENTATION
#include <nanovg/nanovg_gl.h>

(Command: gcc -c working.c -o working.o)

Now, I copy the header files from /usr/include/nanovg/ to the working directory, and replace the code with:

// notworking.c
#include <GL/gl.h>
#include "nanovg.h"
#define NANOVG_GL3_IMPLEMENTATION
#include "nanovg_gl.h"

(Command: gcc -c notworking.c -o notworking.o)

Gcc now complains that some OpenGL functions are not declared:

... (many more similar complaints)
src/nanovg_gl.h: In function ‘glnvg__renderDelete’:
src/nanovg_gl.h:1540:3: warning: implicit declaration of function ‘glDeleteBuffers’; did you mean ‘glSelectBuffer’? [-Wimplicit-function-declaration]
 1540 |   glDeleteBuffers(1, &gl->fragBuf);
      |   ^~~~~~~~~~~~~~~
...

Why does one file compile smoothly but not the other?

A bit deeper: Using the cpp tool, I found that the difference between the two pre-processed files is limited to # directives but I don't see any difference as far as the "C content" goes. Below is a snippet of the pre-processed working.c. If I add the # lines from the pre-processed notworking.c, then gcc no longer compiles the pre-processed working.c and complains about a missing declaration for glDeleteBuffers.

// ... 
 if (gl == 
// # 1533 "src/nanovg_gl.h" 3 4 // <- uncomment this line and glDeleteBuffers is considered missing by gcc
          ((void *)0)
// # 1533 "src/nanovg_gl.h" // <- idem
              ) return;

 glnvg__deleteShader(&gl->shader);



 if (gl->fragBuf != 0)
  glDeleteBuffers(1, &gl->fragBuf); // <- the function that gcc complains about is here
// ...

Edit: Just to make sure that I did not do anything sneaky that might have caused the difference, I followed the following steps which hopefully should be reproducible on another computer:

  1. GCC version: gcc (Ubuntu 10.3.0-1ubuntu1) 10.3.0
  2. Copy the version of GL/gl.h can be found here to working directory and call it glfoo.h
  3. Copy the headers of nanovg (as found in the repo) to /usr/include/nanovg/ and nanovg/ (relative to working directory).
  4. Save the following as test.c in the working dir:
#include "glfoo.h"
#include <nanovg/nanovg.h>
#define NANOVG_GL3_IMPLEMENTATION
#include <nanovg/nanovg_gl.h>
  1. Run gcc -c test.c -o test.o => compilation works
  2. Replace <...> with ".." on lines 2 and 4 and run command => compilation fails.

Just tried these exact steps and I was able to reproduce it.


Solution

  • After investigating this a bit I found the solution. gcc does not apply the same warning level to system headers as it does for "normal" files (this is mainly because system headers are sometimes doing weird things which are not backed up by the C standard, but are "safe" for the platform they are coming with).

    The gcc documentation states (emphasis mine):

    -Wsystem-headers:

    Print warning messages for constructs found in system header files. Warnings from system headers are normally suppressed, on the assumption that they usually do not indicate real problems and would only make the compiler output harder to read. Using this command-line option tells GCC to emit warnings from system headers as if they occurred in user code. However, note that using -Wall in conjunction with this option does not warn about unknown pragmas in system headers—for that, -Wunknown-pragmas must also be used.

    When you include nanovg via <...>, it is treated as a system header.

    So doing gcc -Wsystem-headers working.c actually will bring on the warning.

    Note that your code is neither working in working.c nor notworking.c, as working.c just hides the warning messages. The proper way to access any GL function beyond what is defined in GL 1.1 is to use the GL extension mechanism, which means you have to query the GL function pointers at run-time. Full GL loader libs like GLEW and glad can do that for you automatically. Many of these loaders (including GLEW and GLAD) work by re-#define-ing every GL function name to an internal function pointer, so when you include the header which comes with the loader, every GL function called in your code (and nanovg's) will be re-routed to the loader-libraries function pointers, and your code can actually work (provided you properly initialize the loader at run-time before any of the GL functions is called).