Link errors are particularly nasty because you don't know which source file contains the error.
Under Unix, with the Gnu C++ compiler, the linker ld
generates error messages that look like
collect2: ld returned 2 exit status ld: Undefined symbol add(int &, int &)
This particular message means that the linker was unable to find
a definition of the function named add
that takes
two arguments, both of which are references to an int.
Unix's grep
command can be used to print every line
of source code that mentions the add
function:
% grep 'add' *.h *.cpp ops.h:extern int add (int &, int &); foo.cpp: return add (m, m); ops.cpp:int add (int x, int y) {
From this you can see that add
is declared in
ops.h
, used in foo.cpp
, and defined
in ops.cpp
.
When it was declared, its arguments were declared to be
references to an int.
When it was defined, its arguments were declared to be
of type int.
So this link error is really a type error, caused by defining
add
to take arguments that are of a different type
from the arguments that were declared in the header file.
C++ is statically typed. Why wasn't this type error detected
by the compiler instead of the linker?
Because C++ allows functions to be overloaded. It is
quite all right to define a version of add
that
takes arguments of a type that doesn't match the header file,
so long as you define a matching version somewhere in your
program. Since your program may consist of more than one file,
there is no way for the compiler to know that this is a type
error instead of a legitimate use of function overloading.
What does the following link error mean?
collect2: ld returned 2 exit status ld: Undefined symbol vector<int>::operator=(vector<int> const &)
This means that the linker was unable to find a definition for the copy constructor that converts L-values of type
vector<int> const &into R-values of type
vector<int>You probably didn't declare this copy constructor explicitly, and you probably didn't even realize that you were using it. In C++, these copy constructors are declared implicitly whenever you declare a struct or class. They are called implicitly wherever an L-value must be converted into an R-value. C++ does an awful lot of things like this behind your back.
So what's the problem?
I don't know for certain, because I am fortunate enough not to
understand C++ very well, but I think the C++ compiler was supposed
to define this copy constructor automatically, but has failed to do
so. In other words, I think this is a bug in g++
2.7.2.1.
This bug seems to arise in code such as
student_record sr1; name testName1; seq_grade testGrades1; sr1 = new_student_record(testName1,testGrades1);where a
student_record
is represented as a struct or
class that contains a vector<int>
as one of its
members.
The bug seems to disappear if you rewrite this code as
name testName1; seq_grade testGrades1; student_record sr1 = new_student_record(testName1,testGrades1);
I am reporting this bug to the Free Software Foundation, which maintains the Gnu C++ compiler. I expect they will tell me one of two things:
When I learn more, I will update this web page.
A link error of the form
collect2: ld returned 2 exit status ld: /usr/tmp/cca167822.o: copyString(char *): multiply definedindicates that the program contains two or more definitions of
copyString(char *)
.
In a multi-person project, it is not uncommon for two people to choose the same name for a help function that is not used outside of the file in which it appears. This seems to be the most common cause of the link error shown above.
To prevent link errors, and to hide a module's implementation
more effectively, the static
qualifier should be used
to make help functions invisible outside the file in which they
are defined and used:
static char * copyString (char * s) { ... }
The namespace
feature of C++ can be used instead,
but that seems like overkill for student
programs with no more than a couple of thousand lines of code.
Last updated 2 March 1998.