Skip to content

Instantly share code, notes, and snippets.

@gubatron
Last active October 2, 2024 03:02
Show Gist options
  • Save gubatron/32f82053596c24b6bec6 to your computer and use it in GitHub Desktop.
Save gubatron/32f82053596c24b6bec6 to your computer and use it in GitHub Desktop.
Things to remember when compiling and linking C/C++ programs

Things to remember when compiling/linking C/C++ software

by Angel Leon. March 17, 2015;

Last update on December 14, 2023

Updated on February 27, 2023

Updated August 29, 2019.

Include Paths

On the compilation phase, you will usually need to specify the different include paths so that the interfaces (.h, .hpp) which define structs, classes, constants, and functions can be found.

With gcc and llvm include paths are passed with -I/path/to/includes, you can pass as many -I as you need.

In Windows, cl.exe takes include paths with the following syntax: /I"c:\path\to\includes\ you can also pass as many as you need.

Some software uses macro definition variables that should be passed during compile time to decide what code to include.

Compilation flags

These compilation-time variables are passed using -D, e.g. -DMYSOFTWARE_COMPILATION_VARIABLE -DDO_SOMETHING=1 -DDISABLE_DEPRECATED_FUNCTIONS=0

These compilation time flags are by convention usually put into a single variable named CXXFLAGS, which is then passed to the compiler as a parameter for convenience when you're building your compilation/make script.

Object files

When you compile your .c, or .cpp files, you will end up with object files. These files usually have .o extensions on Linux, on Windows they might be under .obj extensions.

You can create an .o file for a single or for many source files.

Static Library files

When you have several .o files, you can put them together as a library, a static library. In Linux/Mac these static libraries are simply archive files, or .a files. In windows, static library files exist under the .lib extension.

They are created like this in Linux/Mac:

ar -cvq libctest.a ctest1.o ctest2.o ctest3.o

libctest.a will contain ctest1.o,ctest2.o and ctest2.o

They are created like this on Windows:

LIB.EXE /OUT:MYLIB.LIB FILE1.OBJ FILE2.OBJ FILE3.OBJ

Shared Libraries (Dynamic Libraries)

Shared or dynamic libraries, such as .so in Linux, .dylib in Mac, and .dll in Windows, are critical for reducing executable sizes and memory consumption, but they differ in their implementation and behavior.

In Linux, .so files are created like this:

gcc -Wall -fPIC -c *.c
gcc -shared -Wl,-soname,libctest.so.1 -o libctest.so.1.0 *.o
  • -Wall enables all warnings.
  • -c means compile only, don't run the linker.
  • -fPIC means "Position Independent Code", a requirement for shared libraries in Linux.
  • -shared makes the object file created shareable by different executables.
  • -Wl passes a comma separated list of arguments to the linker.
  • -soname means "shared object name" to use.
  • -o <my.so> means output, in this case the output shared library

In Mac, .dylib files are created similarly:

clang -dynamiclib -o libtest.dylib file1.o file2.o -L/some/library/path -lname_of_library_without_lib_prefix

Dynamic Link Libraries (DLLs) in Windows

DLLs in Windows function differently from shared objects in Unix/Linux systems. They tie both exports and imports to specific DLL names, creating a more rigid binding. This difference is essential for understanding how symbols and libraries are managed in different environments.

In Windows, .dll files are created like this:

LINK.EXE /DLL /OUT:MYLIB.DLL FILE1.OBJ FILE2.OBJ FILE3OBJ

Linking with MSVC and Binutils

When linking with MSVC, unlike Unix/Linux linkers where you point directly to the .so, you link against an import library (.lib) or in rare cases the .exp. These import libraries contain import thunks and a symbol index, facilitating the linking process. The .lib format is used by both MSVC and GCC on Linux, but with different contents (COFF files in MSVC, ELF .o files in GCC).

Linking to existing libraries

When linking your software you may be faced with a situation on which you want to link against several standard shared libraries. If all the libraries you need exist in a single folder, you can set the LD_LIBRARY_PATH to that folder. By common standard all shared libraries are prefixed with the word lib. If a library exists in LD_LIBRARY_PATH and you want to link against it, you don't need to pass the entire path to the library, you simply pass -lname and you will link your executable to the symbols of libname.so which should be somewhere inside LD_LIBRARY_PATH.

Tip: You should probably stay away from altering your LD_LIBRARY_PATH, if you do, make sure you keep its original value, and when you're done restore it, as you might screw the build processes of other software in the system which might depend on what's on the LD_LIBRARY_PATH.

What if libraries are in different folders?

If you have some other libbar.so library on another folder outside LD_LIBRARY_PATH you can explictly pass the full path to that library /path/to/that/other/library/libbar.so, or you can specify the folder that contains it -L/path/to/that/other/library and then the short hand form -lbar. This latter option makes more sense if the second folder contains several other libraries.

Useful tools

Sometimes you may be dealing with issues like undefined symbol errors, and you may want to inspect what symbols (functions) are defined in your library.

On Mac there's otool, on Linux/Mac there's nm, on Windows there's depends.exe (a GUI tool that can be used to see both dependencies and the symbol's tables. Taking a look at the "Entry Point" column will help you understand clearly the difference between symbols linking to a shared library vs symbols linking statically to the same library)

Useful command options

See shared library dependencies on Mac with otool

otool -L libjlibtorrent.dylib 
libjlibtorrent.dylib:
	libjlibtorrent.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 120.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1213.0.0)

See shared symbols with nm (Linux/Mac) With nm, you can see the symbol's name list. Familiarize yourself with the meaning of the symbol types:

  • T (text section symbol)
  • U (undefined - useful for those undefined symbol error),
  • I (indirect symbol).

If the symbol is local (non-external) the symbol type is presented in lowercase letters, for example a lowercase u represents an undefined reference to a private external in another module in the same library.

nm's documentation says that if you're working on Mac and you see that the symbol is preceeded by + or - it means it's an ObjectiveC method, if you're familiar with ObjectiveC you will know that + is for class methods and - is for instance methods, but in practice it seems to be a bit more explicit and you will often see objc or OBJC prefixed to those methods.

nm is best used along with grep ;)

Find all Undefined symbols

nm -u libMacOSXUtilsLeopard.jnilib
_CFRelease
_LSSharedFileListCopySnapshot
_LSSharedFileListCreate
_LSSharedFileListInsertItemURL
_LSSharedFileListItemRemove
_LSSharedFileListItemResolve
_NSFullUserName
_OBJC_CLASS_$_NSArray
_OBJC_CLASS_$_NSAutoreleasePool
_OBJC_CLASS_$_NSDictionary
_OBJC_CLASS_$_NSMutableArray
_OBJC_CLASS_$_NSMutableDictionary
_OBJC_CLASS_$_NSString
_OBJC_CLASS_$_NSURL
__Block_copy
__NSConcreteGlobalBlock
__dyld_register_func_for_add_image
__objc_empty_cache
__objc_empty_vtable
_calloc
_class_addMethod
_class_getInstanceMethod
_class_getInstanceSize
_class_getInstanceVariable
_class_getIvarLayout

My C++ code compiles but it won't link

Linking is simply "linking" a bunch of .o files to make an executable.

Each one of these .o's may be compiled on their own out of their .cpp files, but when one references symbols that are supposed to exist in other .o's and they're not to be found then you get linking errors.

Perhaps through forward declarations you managed your compilation phase to pass, but then you get a bunch of symbol not found errors. Make sure to read them slowly, see where these symbols are being referenced, you will see that these issues occur due to namespace visibility in most cases.

Perhaps you copied the signature of a method that exists in a private space elsewhere into some other namespace where your code wasn't compiling, all you did was make it compilable, but the actual symbol might not be visible outside the scope where it's truly defined and implemented.

Function symbols can be private if they're declared inside anonymous namespaces, or if they're declared as static functions.

An example:

Undefined symbols for architecture x86_64:
  "FlushStateToDisk(CValidationState&, FlushStateMode)", referenced from:
      Network::TxMessage::handle(CNode*, CDataStream&, long long, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, bool, bool) in libbitcoin_server.a(libbitcoin_server_a-TxMessage.o)

Here, when I read the code of Network::TxMessage::handle(...) there was a call to FlushStateToDisk, which was declared in main.h, and coded in main.cpp. My TxMessage.cpp did include main.h, the compilation was fine, I had a TxMessage.o file and a main.o, but the linker was complaining.

The issue was that FlushStateToDisk was declared as a static, therefore only visible inside main.o, once I removed the static from the declaration and implementation the error went away and my executable was linked. Similar things happen when functions are declared in anonymous spaces in other files, even if you forward declare them on your local .h

In other cases your code compiles and you get this error linking because your library can't be added using -lfoo, and adding its containing folder to -L doesn't cut it, in this case you just add the full path to the library in your compilation command: gcc /path/to/the/missing/library.o ... my_source.cpp -o my_executable

Reminder:

DO NOT EXPORT CFLAGS, CPPFLAGS and the like on your .bash_profile/.bashrc, it can lead to unintended building consequences in many projects. I've wasted so many hours due to this mistake.

@Petross404
Copy link

Nice article, but how can I find in which library of my Linux, my undefined function exists?

I might use ceil and not know which library I must link with - llibraryname

@gubatron
Copy link
Author

If it's undefined that means it does not exist in the library, so you'll have to grep your source code to know exactly where it exists and figure out what might be the cause why it's not being included in the corresponding .o (object) file.

@iweigele
Copy link

iweigele commented May 1, 2018

@Petross404 In the case of ceil if you try "man ceil" there ought to be this line in the SYNOPSIS section:

Link with -lm.

so you know to have to pass -lm to the linker. You'll find it in libm.so:

>objdump -t /usr/lib/libm.so | grep ceil
0000aa40 l F .text 0000002b __ceil
000232b0 l F .text 0000002b __ceill
0001ab00 l F .text 0000002b __ceilf
0000aa40 w F .text 0000002b ceil
000232b0 w F .text 0000002b ceill
0001ab00 w F .text 0000002b ceilf

("nm -g" and "readelf -s" will also get you the same information in slightly different formats).

If the man page doesn't tell you then you can use guatron's grep idea on the libraries:
>grep -l "\<ceil\>" /usr/lib/*.so
/usr/lib/libm-2.17.so
/usr/lib/libm.so

(-l is lower case ell and specifies name only, the "\<ceil\>" is a regex the '\<' and '\>' are just to specify the start and end of the word, so with them "ceil" and "ceil." would match but "ceiling" would not. They do not have to be paired: "\<ceil" matches "ceilf" and "ceil\>" matches "__ceil". It excludes a bunch of matches from the pthreads library that I didn't want in my output)

@Celthi
Copy link

Celthi commented Sep 28, 2020

Awesome article!

@emgullufsen
Copy link

very useful reference - thanks! -eric

@gubatron
Copy link
Author

@emgullufsen Thank you. I often end up here every few months when I need to maintain some C++ builds.

@npanpaliya
Copy link

Very nice and useful article.

@voltflake
Copy link

Useful cheat-sheet. Thanks!

@gubatron
Copy link
Author

Glad it's useful!

@exoosh
Copy link

exoosh commented Dec 14, 2023

Generally a nice gist. Thanks.

However, I emphatically disagree about lumping together shared objects and DLLs into one section. Yes, their functionality overlaps, but perpetuating this causes people to think they're semantically the same and act accordingly.

They're not the same. With DLLs both exports and imports are always tying a symbol name to a DLL name. On the other hand ld.so on Linux doesn't care about the provenance of the symbols too much (hence LD_PRELOAD can exist) and completely different rules apply about lookup, binding etc.

The gist also glosses over some really important points in relation to its title. While with Binutils and other Unix/Linux linkers you literally point them to the .so to be linked, with MSVC you have the indirection of import libraries. Also with a .lib extension. They contain the import thunks and a symbol index. With MSVC you don't link against the .dll, you link either against the import library - some .lib - or in rare cases the .exp. The outcome is functionally the same, but

Both forms of .lib are the same file format - and that same file format is also used and read by ar. The difference is that with MSVC you'll typically find COFF files inside, while with GCC on Linux you'll typically find ELF .o files.

PS: it's been too long and I have no access to a macOS system to say anything about .dylib, though.
PPS: you can even merge an import and static lib into a single one.

@gubatron
Copy link
Author

@exoosh Thank you so much for this comment, I wrote this such a long time ago from a position of much ignorance, wish I had more experience with C++ builds. I'll try to include your feedback and edit it. Wonder if you can try and edit the gist so that I can accept a merge of it.

@gubatron
Copy link
Author

updates are in, hope they addressed your feedback

@exoosh
Copy link

exoosh commented Dec 18, 2023

Hey, nice. I never worked with Gists that extensively. Will see if I can send a PR of sorts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment