Programmers inadvertently introduce several different kinds of errors into the programs they write. Although functions can contain any of the listed errors, they are the primary cause of assembly (aka, linker or loader) errors. The previous discussion of assembly errors focused on a minor problem arising only on Windows systems in a specific situation. This section expands on the earlier discussion with more authentic, albeit small, examples.
Contemporary programs and program systems can consist of thousands or millions of lines of code, typically spanning many files. For example, an office automation suite consists of a bundled set of related applications, including a word processor, a spreadsheet, a presentation program, and a database. To eliminate duplicate code and provide a consistent user interface, the programs often share some files. The compiler component individually translates each source code file to machine code and saves it in an object file. The linker or loader assembles the object files into executable programs.
Assembling object files is a relatively fast operation, faster than compiling source to object code. So, another advantage of this approach is that programmers don't need to recompile unmodified source code files to assemble an executable program. However, the approach also allows assembly errors 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 functions defined in other files. It uses the prototypes to validate the calls, ensuring that they match the definition. If a programmer modifies a function header in one file but fails to update the corresponding prototype in a file calling the function, an assembly (i.e., linker or loader) error occurs. Alternatively, the programmer may update the prototype in the calling file but fail to recompile it to object code. The most frequent mismatches are the function name (including capitalization) and the number or type of parameters.
| file1: Definition | file2: Call |
|---|---|
#include <iostream> using namespace std; void |
#include <iostream> using namespace std; void foo( |
| (a) | (b) |
| Diagnostics | |
1>------ Build All started: Project: linkerrors, Configuration: Debug Win32 ------ 1>file2.cpp 1>file1.cpp 1>D:\tmp\CPP\link\linkerrors\linkerrors\file2.cpp(8,12): error C2660: 'foo': function does not take 2 arguments 1>D:\tmp\CPP\link\linkerrors\linkerrors\file2.cpp(4,6): message : see declaration of 'foo' 1>D:\tmp\CPP\link\linkerrors\linkerrors\file2.cpp(9,2): error C3861: 'Foo': identifier not found 1>Generating Code... 1>Done building project "linkerrors.vcxproj" -- FAILED. ========== Build All: 0 succeeded, 1 failed, 0 skipped ========== file1.cpp file2.cpp file2.cpp(8): error C2660: 'foo': function does not take 2 arguments file2.cpp(9): error C3861: 'Foo': identifier not found Generating Code... |
|
| (c - Windows) | |
/usr/bin/ld: file2.o: in function `main': file2.cpp:(.text+0x13): undefined reference to `foo(int, int)' /usr/bin/ld: file2.cpp:(.text+0x27): undefined reference to `Foo(int, int, int)' collect2: error: ld returned 1 exit status |
|
| (d - Linux) | |
No programming language or device can prevent programmers from putting bugs in their programs. However, they can take steps to reduce the errors arising from mismatched function definitions and calls spanning files. The steps rely on an established programming practice and widely available programming tools. The Chapter 5 version of the Time example demonstrated using a programmer-written header file. Header files often contain structure or class specifications, but, as the following figure illustrates, they can consist solely of prototypes.
Projects and makefiles allow programmers to describe the dependencies between the various files comprising a program. Used with header files, they keep dependent object files up to date. These tools compare the timestamps (i.e., the time the file was last modified) of the dependent and independent files. Whenever a dependent object file is out of date (i.e., older than any of the files it depends on), the tool recompiles it.
|
#include <iostream> |
| (a - example.h) | |
#include <iostream> using namespace std; |
|
| (b - file1.cpp) | (c - file2.cpp) |
| Visual Studio Project | Makefile |
|---|---|
![]() |
|
| (a) | (b) |
g++ -c builds the object files, while g++ -o builds the executable by calling the loader, ld, and passing to it the object files and appropriate command-line options. The make command searches, in order, for files named "Makefile" and "makefile," opening and processing the first it finds.