4

I'm trying to create a C library, and a C++ test program for the library using Boost::Test. The code that I posted in this question is a simplification of my code, and it displays the exact same problem. Please help!

Here's the directory structure. childlib is the library I'm trying to create, and test is the test program.

> tree
.
`-- src
    |-- main
    |   |-- c
    |   |   `-- childlib.c
    |   |-- childlib.h
    |   `-- makefile
    `-- test
        |-- cpp
        |   `-- test.cpp
        `-- makefile

5 directories, 5 files

I can make childlib into a static library successfully:

> cd src/main    
> make
gcc -c -fPIC -o c/childlib.o c/childlib.c
ar rcs libchildlib.a c/childlib.o
ranlib libchildlib.a

But I can't make my test program by linking with it:

> cd ../test/
> make
g++ -I. -I../main -Imy_boost_install_dir/include -c -std=c++11 -o cpp/test.o cpp/test.cpp
g++ -L../main -Lmy_boost_install_dir/lib -lchildlib -lboost_unit_test_framework -Wl,-rpath=my_boost_install_dir/lib -o test_childlib cpp/test.o 
cpp/test.o: In function `test_func1::test_method()':
test.cpp:(.text+0x15e7e): undefined reference to `childlib_func1()'
collect2: error: ld returned 1 exit status
make: *** [test_childlib] Error 1

On the other hand, if I run the compilation manually by removing the reference to the static lib and adding childlib.c as a source file, I can make the test program successfully:

> g++  -Lmy_boost_install_dir/lib -lboost_unit_test_framework -Wl,-rpath=my_boost_install_dir/lib -o test_childlib cpp/test.o ../main/c/childlib.c 
> ./test_childlib 
Running 1 test case...

*** No errors detected

Here's the various source files. test.cpp:

#include <cstdlib>
#include <stdint.h>

#define BOOST_TEST_MODULE childlib test
#include <boost/test/unit_test.hpp>
#include <boost/test/included/unit_test.hpp>

#include "childlib.h"

BOOST_AUTO_TEST_CASE( test_func1 ) {
  childlib_func1();

  BOOST_CHECK(true);
}

childlib.h:

#ifndef CHILDLIB_H_
#define CHILDLIB_H_

void childlib_func1();

#endif /* CHILDLIB_H_ */

and finally childlib.c:

#include <stdint.h>

void childlib_func1() {
  return;
}

The two makefiles are below.

childlib makefile: Eventually I want to also create a dynamic library but right now it's commented out of make all:

# childlib

CC = gcc

SRCS             = $(wildcard c/*.c)
OBJS             = $(SRCS:.c=.o)
HDRS             = $(wildcard *.h)
MODULE_BASE_NAME = childlib
MODULE_LIB_A     = lib$(MODULE_BASE_NAME).a
MODULE_LIB_SO    = lib$(MODULE_BASE_NAME).so
INCLUDE_DIRS     = .
INCLUDE          = $(addprefix -I,$(INCLUDE_DIRS))
CFLAGS           = -c -fPIC


all: $(MODULE_LIB_A) # $(MODULE_LIB_SO)

$(MODULE_LIB_A): $(OBJS)
    ar rcs $@ $^
    ranlib $@

$(MODULE_LIB_SO): $(OBJS)
    $(CC) -shared -o $@ $^  

.c.o:
    $(CC) $(CFLAGS) -o $@ $^

clean:
    -rm -f $(MODULE_LIB_A) $(MODULE_LIB_SO) $(OBJS)
    -find . -name '*~' -delete

Here's the test harness makefile:

# test

CC        = g++

SRCS             = $(wildcard cpp/*.cpp)
OBJS             = $(SRCS:.cpp=.o)
MODULE_EXE       = test_childlib

MAIN_DIR         = ../main

BOOST_DIR        = my_boost_install_dir
BOOST_INC_DIR    = $(BOOST_DIR)/include
BOOST_LIB_DIR    = $(BOOST_DIR)/lib
BOOST_LIBS       = boost_unit_test_framework
INCLUDE          = $(addprefix -I,. $(MAIN_DIR) $(BOOST_INC_DIR))
LIB_DIRS         = $(addprefix -L,$(MAIN_DIR) $(BOOST_LIB_DIR))
LIBS             = $(addprefix -l,childlib $(BOOST_LIBS))
LINKER_OPTS      = -Wl,-rpath=$(BOOST_LIB_DIR)

CFLAGS           = -c -std=c++11


all: test

test: $(MODULE_EXE)
    ./$(MODULE_EXE)


$(MODULE_EXE): $(OBJS)
    $(CC) $(LIB_DIRS) $(LIBS) $(LINKER_OPTS) -o $@ $^ 

.cpp.o:
    $(CC) $(INCLUDE) $(CFLAGS) -o $@ $^

clean:
    -rm -f $(MODULE_EXE) $(OBJS)
    -find . -name '*~' -delete

I'm using gcc & g++ 4.8.1. I'm also using boost 1.54.0 compiled with gcc 4.7.2.

It seems like I need to just provide the right options to g++ in my test makefile, but I don't know what they are. Can someone please help me link the childlib library with my test program?

Avi Tevet
  • 778
  • 1
  • 7
  • 13
  • `-fPIC` flag is to be used with __shared__ libraries, not static ones. – Chnossos May 19 '14 at 22:49
  • @Chnossos - I tried removing -fPIC from the childlib makefile, then cleaning/rebuilding both modules. Making the test harness fails the same way. – Avi Tevet May 19 '14 at 22:54
  • I changed the compiler of childlib to g++ instead of gcc, and I can link with the test harness! I still want to compile childlib as a C library instead of C++, but maybe this helps point to the answer. I'll start researching how to link a C library with a C++ exe. – Avi Tevet May 19 '14 at 23:03
  • Related: https://stackoverflow.com/a/409470/6064933 – jdhao Jul 03 '21 at 13:23

4 Answers4

6

I haven't seen all your code, but the problem is most likely caused by how static linking works (http://eli.thegreenplace.net/2013/07/09/library-order-in-static-linking/).

Try changing the order of the libraries, and specifically, try changing this:

$(MODULE_EXE): $(OBJS)
  $(CC) $(LIB_DIRS) $(LIBS) $(LINKER_OPTS) -o $@ $^ 

into this:

$(MODULE_EXE): $(OBJS)
  $(CC) $(LIB_DIRS) $(LINKER_OPTS) -o $@ $^ $(LIBS)
Ivan Vergiliev
  • 3,771
  • 24
  • 23
  • Thanks for the suggestion. I just tried it, as well as swapping the library order, and got the same failure (`g++ -L../main -Lmy_boost_install_dir/lib -Wl,-rpath=my_boost_install_dir/lib -o test_childlib cpp/test.o -lboost_unit_test_framework -lchildlib` and `g++ -L../main -Lmy_boost_install_dir/lib -Wl,-rpath=my_boost_install_dir/lib -o test_childlib cpp/test.o -lchildlib -lboost_unit_test_framework`) – Avi Tevet May 19 '14 at 22:56
  • I'm marking this as the answer because this was part of the answer. The other part was wrapping the header include with extern "C": `extern "C" {#include "childlib.h"};` – Avi Tevet May 19 '14 at 23:09
4

Libraries and object are processed in the order encountered on the linking command line: object files (or source files after transformation into object files) are always included. Their undefined symbols are added to the list of symbols to be resolved. Libraries are inspected to look for any undefined symbols the point they are encountered. Any object file from a library being investigated is included if it defines, at least, one symbol which is undefined so far. Once processed, the libraries are otherwise forgotten.

tl;dr: put the object file first, the libraries last.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • It seems that for shared library, the order does not matter. For example, if I have `main.o` which uses a function from `libfoo.so`. Both `g++ main.o libfoo.o -o main` and `g++ libfoo.o main.o -o main` works. Any idea why? – jdhao Jul 03 '21 at 09:07
  • @jdhao: you explicitly instruct the compiler to inclise the two objects. The library logic of only including necessary part applies when specifying the library. On UNIXes that normally tales the form of `-lfoo` for an object named `libfoo.a` or `libfoo.so`. – Dietmar Kühl Jul 03 '21 at 13:34
  • I upvoted your commend a year ago, forgot about it, then faced the problem again, only to google your very answer again, and it was already upvoted by me. You helped me not only once, but twice. – desertkun Oct 22 '21 at 18:35
2

In the "on the other hand" case, you compile childlib with g++.

In the original case you compile it with gcc.

C and C++ are different languages. You should decide which language you want to use for childlib and stick with it, rather than try to write code that fits within the common subset of both languages.

There's no problem linking C-generated object files with C++-generated object files, so long as you specify that either the C++ object files should use C-compatible formatting, or that the function name they need to link against is in a C-generated object file.

Suppose you want to keep childlib.c as C code. The latter is more usual and the way to do it is to make sure that all declarations and definitions and within childlib.h are wrapped in extern "C" { ...your code... } whenever they are included from a C++ source file. One way to achieve this is to have the header contain:

#ifdef __cplusplus
extern "C" {
#endif

// the code

#ifdef __cplusplus
}
#endif

Then the header file always works in both languages, and you do not have to rely on remembering to do extern "C" { #include... in all files that include it. Also it allows you to have things outside of the extern block, e.g. including system headers.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • I want childlib to be written in C. I tried compiling childlib with C++ because I had already tried everything I knew (aka not much) with no success. That experiment led me down the path to the eventual answer of wrapping the include in an `extern "C"` block. But this way looks better so I'll do this. – Avi Tevet May 20 '14 at 05:45
0

I am aware that you are not using automake but in my case, I needed to rename my *_LIBRARIES targets to *_LTLIBRARIES so that the Makefile would use libtool to build *.la files instead of *.a files.

For some reason, when I used the other syntax, the nested static library would not be added properly to the top-level static library (an nm dump on the top library resulted in a "File format not recognized error" when reading the nested library inside it) resulting in the linker error in your question, or I would just get errors while it invokes ar and prints its help message.

Zenul_Abidin
  • 573
  • 8
  • 23