Avi Tevet
Avi Tevet

Reputation: 828

Undefined reference when linking with static library, but successful link when compiling with src

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?

Upvotes: 5

Views: 12204

Answers (4)

Zenul_Abidin
Zenul_Abidin

Reputation: 809

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.

Upvotes: 0

M.M
M.M

Reputation: 141534

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.

Upvotes: 2

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153792

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.

Upvotes: 4

Ivan Vergiliev
Ivan Vergiliev

Reputation: 3841

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)

Upvotes: 6

Related Questions