Solution to most not-found or undefined problems in C/C++ compilation

Many people have compilation errors. If you search "undefined reference to" in Google, or maybe StackOverflow alone, you will notice that those problems have been asked dozens if not hundreds of times. There are generally 4 problems:
  • was not declared in this scope
  • no such a file or directory
  • undefined reference to
  • error while loading shared library, or cannot open shared object file

The thing is actually very simple: the compiler is not told to find the right file at the right place. The discussion below is based on Linux using GCC for C and C++.

Problem 1: was not declared in this scope

Usually, this problem has nothing to do with your system settings or compiler configuration. It's a problem within your source code. On the top level, the function is not defined. The reason can vary.

One reason is that the file declaring (or defining, if the declaration is done together with definition) the functions is not specified, e.g., the header file is not included. This can be easily fixed by including such a file in your source code.

Another reason is that you used the function beyond its class or namespace.

Problem 2: No such a file or directory for a header file


If a header file is not found, the preprocessor or the preprocessor part of the compiler, will raise the error No such a file or directory. Depends on where the header files are supposed to be at, there are different solutions.

Most C/C++ compiles treat the quote form of #include and angle-bracket form of #include differently. Simply running
$ echo | gcc -E -Wp,-v -
will tell you the difference:
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/5/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/5/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include

Those directories listed under the section #include <...> search starts here: are referred to as standard system directories [1]. They are from the convention of Linux systems' file hierarchy.

If you want to expand the search for angle-bracket form #include<...>, use the -I option. The preprocessor will search header files in directories followed by -I option before searching system directories. Hence, a header file in a -I-option directory will override its counterpart in system directories and be used to compile your code.

For quote form #include"...", the preprocessor will first search in the current directory that the source file is at and then all the directories specified after the -iquote option.

The example below will show you how -I and -iquote append search paths:
l$ echo | gcc -iquote/home/forrest/Downloads -I/home/forrest/Dropbox -E -Wp,-v -
#include "..." search starts here:
 /home/forrest/Downloads
#include <...> search starts here:
 /home/forrest/Dropbox
 /usr/lib/gcc/x86_64-linux-gnu/5/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/5/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include

So, if you have the following macro in your code called mycode.c:
#include < xyz.h>
#include "abc/xyz.h"
and your GCC command is like this
gcc mycode.c -I/opq/ -iquote/uvw
then the GCC's preprocessor (again, it's part of the GCC) will look for header files in the following full paths (on Ubuntu Linux 16.04) in addition to the search in system directories:
/opq/xyz.h
 /uvw/abc/xyz.h
According to GCC document[1], it searches for header files in quote form #include"..." before searching for angle-bracket form #include"...".

Further specifications can be achieved using two other options -isystem and -idirafter. They are all well documented in GCC document[2].

Problem 3: Undefined reference to

You probably see an error like this
face.cpp:(.text._ZN2cv3Mat7releaseEv[_ZN2cv3Mat7releaseEv]+0x4b): undefined reference to `cv::Mat::deallocate()'
collect2: error: ld returned 1 exit status
This is a link-time error when the linker ld, which is called automatically when you use GCC, cannot find the binary library that contains at least one function called by your code.

To fix, first tell the compiler (actually it's linker part) the path containing library files using the -L option [2] and then library file names using the -l option [4]. If we denote the values after -l and -L options as X and Y respectively, the compiler will search for every file called libY.so under every directory X. That's why on (almost) all Linux systems, a shared library file begins with lib and ends with the appendix .so, such as libopencv_core.so.

For example, the command
$ g++ face.cpp -L/opencv/lib -lopencv_core -lopencv_videoio
will ask the linker to find the following binary library files:
/opencv/lib/libopencv_core.so
/opencv/lib/libopencv_videoio.so

Note that you may not be able to use some shell directives, such as ~ after the -L option.

In most cases, you do not need to use the -L and -l options because the compiler (again, its linker part) automatically searches in a set of system directories. When you have to, some tools can help you, such as pkg-config. I will write another blog post.

Problem 4: error while loading shared libraries or cannot open shared object file


Now, your program has been successfully compiled. When running it, you probably see an error like this:
./a.out: error while loading shared libraries: libopencv_core.so.3.3: cannot open shared object file: No such file or directory 
This problem is from the loader. Unlike the link-time error above, this problem is a run-time error. The shared library filenames are usually hardcoded into your binary program. To fix it, simply tell the loader where to find the specified shared library files. There are multiple ways.

Solution 1: Most Linux systems maintain an environment variable LD_LIBRARY_PATH and the loader will search for binary library files requested by a program in all directories listed in LD_LIBRARY_PATH, on top of a set of standard system directories, such as /lib or /usr/lib. To set LD_LIBRARY_PATH is like how you set any other environment variables, e.g., export on the Shell or edit and source ~/.bashrc.

Solution 2: Most Linux systems also maintains a shared library cache by a program called ld-config. It remembers where to find the default location of each shared library file. Simply run
$ ldconfig -p
it will tell you the mapping from shared library files to absolute paths in the system, e.g.,
2591 libs found in cache `/etc/ld.so.cache'
 libzzipwrap-0.so.13 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzzipwrap-0.so.13
 libzzipmmapped-0.so.13 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzzipmmapped-0.so.13
 libzzipfseeko-0.so.13 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libzzipfseeko-0.so.13

To change the mapping, simply edit /etc/ld.so.conf and then run
$ sudo ldconfig
to rebuild the cache.

Solution 3: The two changes above are applied system-wide. A more flexible way is to specify the location of the shared libraries when linking your code using the
-Wl,-rpath
option, e.g.,
g++ face.cpp -idirafter ~/Downloads/opencv_TBB_install/include -L/home/forrest/Downloads/opencv_TBB_install/lib -lopencv_core -lopencv_objdetect -lopencv_highgui -lopencv_imgproc -lopencv_videoio -Wl,-rpath=/home/forrest/Downloads/opencv_TBB_install/lib

The disadvantage of Solution 3 is that if you change the location of the shared library, then you will run into error again.


References:
[1] Filesystem Hierarchy, https://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard
[2] GCC options for directories https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html
[3] Shared Library How-To, http://tldp.org/HOWTO/Program-Library-HOWTO/shared-libraries.html
[4] GCC options for linking, https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html

No comments: