Link Errors

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.


Undefined copy constructors.

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:

  1. Oh, yes, we've known about this bug for months; it is already fixed in our next release.
  2. No, that's not a bug, you just don't understand C++. Go read chapter 12 of the draft standard.

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 defined
indicates 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.