6.10. Functions And Link Errors

Time: 00:06:44 | Download: Large, Large (CC), Small | Streaming Streaming (CC) | Slides (PDF)
Review

A variety of errors can creep into our programs. The compiler component and the linker (or the loader on POSIX systems) detect and report syntax and link errors, respectively. But the compiler components' abilities to localize the errors (i.e., to locate or find where the errors occur in the program) are not equal. A brief review of syntax errors makes understanding link errors easier.

Syntax Errors

The compiler component, the middle program in the full compiler system, detects and reports syntax errors. Its diagnostics include the names of the files containing errors, the line numbers where it first detects an error and where programmers begin searching for it, and the compiler's "best guess" about the errors' cause. Sometimes, the compiler's diagnostics (i.e., its error messages) accurately describe the error but are inaccurate and confusing at other times. When the error messages don't help us identify the problems, we begin at the line number reported in the error message and work upward toward the top of the file, using our understanding of C++ syntax to find the problem.

A screen capture showing the Visual Studio output window, which is displaying a syntax error message. An error message, suggesting that the program is missing a ';' on line 8, is highlighted.
Syntax error diagnostic output. Visual Studio reports error messages in two tab-selectable panes - "Error List" and "Output." I first used the "Output" pane, and I prefer it. If the number of error messages in the pane exceeds its size, a scroll bar will appear on the right side. Scroll to the top of the pane and correct the errors from top to bottom. Double-clicking an error message (e.g., the one highlighted in blue) navigates quickly to the error in the editor window, as the following figure illustrates.
A screen capture showing part of a program in the Visual Studio editor. The program consists of two files; the file containing a syntax error is open. A marker indicates the line where the compiler component detected a missing semicolon.
Basic syntax debugging. Double-clicking an error message in the output window:
  1. displays the correct file in the editor
  2. moves the cursor to the line where the error was detected
  3. marks the line in the editor where the linker detected the error
  4. In this example, the error actually occurred on the previous line (missing semicolon).
Syntax Error Debugging Tips
  1. One error can cause multiple error messages, so it's easiest to start with the first error message and correct the errors, one at a time, downward in the list
  2. The compiler often skips some code following a syntax error. If you correct one error but the compiler reports more errors than it did before the correction, it just means that the compiler is now checking code it previously skipped
  3. Error messages corresponding to syntax errors include the line number where the error was detected. Syntax errors are either on that line or above it, but they are never located below the reported line number

Although incorrect syntax doesn't cause link errors, the compiler system still reports them in the output window. We first looked at link errors in the narrow context of building single-file programs. Multi-file programs give us more opportunities to make errors that the linker catches and reports.

The three programs of the full compiler system (Figure 4) process program files in different ways.

The potential for link errors arises when a program defines a function in one file but calls it in another - a typical situation in multi-file programs. When the compiler component processes a source code file, it needs function declarations or prototypes for the functions defined in other files. The linker or loader combines all the object files - the files defining and calling each function - to create an executable program. The linker detects and reports the error when the function call and definition don't match. Link errors are denoted in the output window by the label "LNK" followed by a number, but don't worry about the number (it doesn't help identify the error); the loader reports errors with the label "ld."

The Link Error Solution

Labeling the last section as a "Solution" is presumptuous. No programming language or construct can prevent a determined programmer from writing buggy code! Nevertheless, there are steps that we can take to help minimize the impact of prototype errors and make them easier to correct. Chapter 5 version of the Time example demonstrated a program with one header and two source code files. Although that header file contained a structure specification and function prototypes, it's possible to create header files containing only function prototypes.

void foo(int x, int y, int z);
#include <iostream>
#include "example.h"
using namespace std;

int main()
{
    foo(10, 20);	// link error
    Foo(10, 20, 30);	// link error

    return 0;
}
 
 
(a: example.h)
#include <iostream>
#include "example.h"
using namespace std;

void foo(int x, int y, int z)
{
    cout << x << "  " << y << " " << z << endl;
}
(b: file1.cpp)(c: file2.cpp)
Detecting link errors with header files. Putting function prototypes in header files is most beneficial when programs define functions in one file and call them from another. Authentic programs frequently organize functions around data structures; object-oriented programming strengthens this approach by creating classes with member functions. Including a common header file in each source code file ensures that the compiler bases every function call on a single, correct, and consistent prototype. If the function definition and prototype agree, including a prototype in the source code file defining the function does not cause a problem. Making a prototype error in a header file will cause many linker errors but only requires correcting one line in one file. Putting prototypes in header files makes detecting and correcting mismatches between function definitions and calls much easier.
  1. Put the correct prototype to a header file named example.h.
  2. Including the prototype header file here is unnecessary but doesn't cause a problem. This behavior is helpful when the header file contains many prototypes of functions defined and called throughout the program.
  3. #include the header in all files calling the foo function.
The #include "example.h" directive may follow the namespace statement. Stylistically, I like to keep my include directives together. However, if the prototype depends on a feature declared in another header file, the include directive must follow the feature's include.