Fixing the ‘Undefined Reference’ Errors for C/C++ Projects

This tutorial shows how to various problems related to missing symbols in C/C++ projects. We will create a basic project calling the png_create_read_struct() function from the libpng library and will go through common missing setup steps, explaining the errors that will arise.

Before you begin, install VisualGDB 5.4 or later.

  1. Start Visual Studio and locate the VisualGDB Linux Project Wizard:
  2. Pick a name and location for your project:
  3. Press “Create” to launch the VisualGDB-specific part of the wizard. On the first page, pick Create a new project -> Application -> MSBuild:
  4. In this tutorial we will use a cross-toolchain that runs on Windows and builds code for a Linux target and will demonstrate points of failure caused by this configuration. However, most of the steps shown below will work for projects built directly on Linux as well:
  5. Press “Finish” to generate the project. Now we will try calling a function from the libpng library and will go through most possible missing steps that could cause a build-time or run-time error. Add the following code to the main() function and try building the project:
  6. The build will now fail with the following error:

  7. You can find out the exact output from GCC by right-clicking on the error message and selecting “Go to Build Log”:
  8. The “‘png_create_read_struct’ was not declared in this scope” error means that the C/C++ compiler does not know the png_create_read_struct symbol. It cannot tell whether it is a function, global function pointer or a preprocessor macro. To resolve this error, the png_create_read_struct() function needs to be declared. A declaration for it would look as follows:
  9. As it is not practical to copy each function declaration into every source file, most C/C++ libraries provide header files that list all the relevant declarations. You can usually find out the header file name in the documentation and include it into your program as shown below:

    This will be equivalent to copying and pasting the header file contents into your source file.
  10. Including a header file will not search the entire build machine for it. Instead, GCC will only search a few common places (e.g. /usr/include) and the header search directories specified in your project’s settings. In our example, the png.h file is located in toolchain’s /usr/include/libpng directory and won’t be found initially, producing the following error:

  11. VisualGDB’s will normally search for the missing headers in the nearby directories and will suggest fixing the settings automatically. If you are configuring the project manually, simply locate the header file on your build machine and add its directory to the Include Directories field:Note that if you are using a cross-toolchain, the header must be present in its sysroot directory (e.g. /usr/include/libpng/png.h corresponds to E:\sysgcc\raspberry\arm-linux-gnueabihf\sysroot\usr\include\libpng\png.h). If you are using a cross-toolchain that runs on Windows, you would need to specify the path to the file on the Windows machine (i.e. $(ToolchainDir)\arm-linux-gnueabihf\sysroot\usr\include\libpng\png.h). For convenience, GCC automtaically replaces ‘=’ at the beginning of the path with the sysroot directory, so the correct include directory in this example would be =/usr/include/libpng/png.h (note the ‘=’ at the beginning).
  12. If you build the project now, it will fail with another error:

  13. This happens because the png.h file only contains a declaration for theĀ png_create_read_struct() function. I.e. it defines its return type and argument types (letting GCC check them at compile time), but it does not provide an implementation for it. The function implementations are typically located in the static libraries (.a files) or shared libraries (.so files). You can find the correct library name by searching for all .a and .so files that contain the png_create_read_struct text. If you have found several libraries, use the objdump -T <.so file> or objdump -t <.a file> command to list all symbols exported by that library:

    If the symbol you are looking for is listed with a non-zero address (first column), you have found the correct library. Otherwise, that library is importing that symbol, not exporting it.
  14. In this example, the library defining png_create_read_struct() is called You can link it into your project by adding its name (without the lib prefix) to the “Library Names” field, as long as it is located in a standard library directory. If not, you would also need to add the library’s directory to the Library Directories field:
  15. Now you can build the project successfully:If the build still fails, you might be missing an extern “C” clause in the headers. See this tutorial for a detailed step-by-step explanation.
  16. Finally, we will demonstrate a runtime error. Deploy the built program to your target and run “ld <Binary Name>”:
  17. The LDD command will show the shared libraries required by that file. In this tutorial we will move all the libpng* libraries to /usr/lib/arm-linux-gnueabihf/png subdirectory and will show what will happen. Once you have moved the library, start debugging the project with VisualGDB and observe the following error message in the Debug Output:

    VisualGDB will automatically suggest locating the file on the target:
  18. Locate the library in the directory where you moved it and press “OK”:
  19. This will automatically add the directory of the library to the project’s LD_LIBRARY_PATH variable, so now you will be able to launch it successfully:
  20. If you are running the project manually, simply add the directory of the missing .so file to the LD_LIBRARY_PATH variable as shown below and you will be able to launc it as well:As we have passed NULL to png_create_read_struct() instead of the libpng version number, it will show a warning message. You can eliminate it by passing PNG_LIBPNG_VER_STRING as the first argument to png_create_read_struct().