3

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

10
  • Looks like your make rule builds the library first. can you provide your full make output? Commented Mar 4, 2016 at 15:25
  • @UmamaheshP Edited question. Commented Mar 4, 2016 at 22:59
  • 2
    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 -lds Commented Mar 5, 2016 at 2:19
  • 1
    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. Commented Mar 5, 2016 at 3:26
  • 1
    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. Commented Mar 5, 2016 at 21:12

2 Answers 2

3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Note that this changed: I distincly recall that in older versions of gcc you where able to put libraries first and gcc would implicitly permutate the operand order.
How can I change that within the makefile? It would seem to me that line 34 is already appending the .a to the end of CFLAGS, however it seems that internally Make does something like $(CC) $(CFLAGS) $(the target). How would I tell it to put the library after the target?
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.