3
votes

I'm following the "Learn C the Hard Way" ebook(?), and reached exercise 32.

It uses a previously developed project structure, where tests link to the built library. However, when I run make test, I get "undefined reference to X", where X is every function defined in my library's header.

twoll_tests.c is my test file, libds is the library. See project tree at the bottom of this question.

The compilation line for the tests is this:

cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -std=c11 build/libds.a tests/twoll_tests.c -o tests/twoll_tests

Makefile contents:

CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -std=c11 $(OPTFLAGS)
LIBS=-ldl $(OPTLIBS)
PREFIX?=/usr/local

SOURCES=$(wildcard src/**/*.c src/*.c)
OBJECTS=$(patsubst %.c,%.o,$(SOURCES))

TEST_SRC=$(wildcard tests/*_tests.c)
TESTS=$(patsubst %.c,%,$(TEST_SRC))

TARGET=build/libds.a
SO_TARGET=$(patsubst %.a,%.so,$(TARGET))

# The Target Build
all: $(TARGET) $(SO_TARGET)

dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS)
dev: all

$(TARGET): CFLAGS += -fPIC
$(TARGET): build $(OBJECTS)
    ar rcs $@ $(OBJECTS)
    ranlib $@

$(SO_TARGET): $(TARGET) $(OBJECTS)
    $(CC) -shared -o $@ $(OBJECTS)

build:
    @mkdir -p build
    @mkdir -p bin

# The Unit Tests
.PHONY: test
test: CFLAGS += $(TARGET)
test: $(TESTS)
    sh ./tests/runtests.sh

valgrind:
    VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE)

# The Cleaner
clean:
    rm -rf build $(OBJECTS) $(TESTS)
    rm -f tests/tests.log
    find . -name "*.gc*" -exec rm {} \;
    rm -rf `find . -name "*.dSYM" -print`

# The Install
install: all
    install -d $(DESTDIR)/$(PREFIX)/lib/
    install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/

# The Checker
BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)'
check:
    @echo Files with potentially dangerous functions.
    @egrep $(BADFUNCS) $(SOURCES) || true

And finally, the project tree:

.
├── bin
├── build
│   ├── libds.a
│   └── libds.so
├── LICENSE
├── Makefile
├── README.md
├── src
│   └── libds
│       ├── twoll.c
│       ├── twoll.h
│       └── twoll.o
└── tests
    ├── runtests.sh
    ├── tester.h
    ├── tests.log
    └── twoll_tests.c

EDIT: Here's the full output of make test:

kroltan@kroltan ~/Projects/learncthehardway/ex32 $ make all
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -std=c11  -fPIC   -c -o src/libds/twoll.o src/libds/twoll.c
ar rcs build/libds.a src/libds/twoll.o
ranlib build/libds.a
cc -shared -o build/libds.so src/libds/twoll.o
kroltan@kroltan ~/Projects/learncthehardway/ex32 $ make test
cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -std=c11  build/libds.a    tests/twoll_tests.c   -o tests/twoll_tests
/tmp/ccnOW9OX.o: In function `twoll_new_test':
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:8: undefined reference to `twoll_new'
/tmp/ccnOW9OX.o: In function `twoll_del_test':
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:13: undefined reference to `twoll_new'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:13: undefined reference to `twoll_del'
/tmp/ccnOW9OX.o: In function `twoll_push_pop_test':
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_new'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_push'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_push'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_push'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_pop'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_pop'
/home/kroltan/Projects/learncthehardway/ex32/tests/twoll_tests.c:18: undefined reference to `twoll_pop'
collect2: error: ld returned 1 exit status
make: *** [tests/twoll_tests] Error 1

2
Looks like your make rule builds the library first. can you provide your full make output?Umamahesh P
@UmamaheshP Edited question.Kroltan
If you see the library libds.a in builds dir,try this command. cc -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG -std=c11 tests/twoll_tests.c -o tests/twoll_tests -L builds -ldsUmamahesh P
Happy to know that it worked. But I think a full library path should also work. I have not looked into that yet. Will add an answer later if required. Thanks.Umamahesh P
the key factor in making it work is that referenced libraries must be listed last in the gcc link step. This is because gcc processes the line from left to right and since the library was listed before the object files, when the library was listed, there were no unresolved references. Moving the library reference to after the object files results in their being unresolved references that the library will resolve.user3629249

2 Answers

3
votes

This part of the compiler invocation:

build/libds.a    tests/twoll_tests.c

is bad; you always want the libraries after the source files. Think of it like this:

  1. Source file creates references to symbols
  2. Libraries resolve references to symbols

You can't resolve something before it's been created, so the libraries should go last.

0
votes

With the help of @unwind and @Umamahesh P, it was found that the library must be added after the target when invoking the compiler. After some extra searching around the internet, I've found that the correct Make variable for libraries is actually LDLIBS, not LIBS as was given on the book. I've changed the variable usage, and added the following assignment to my test target:

test: LDLIBS += $(TARGET)

With just that, linking is successful.