Various issues arise during debugging that do not deal with debugging tools. We'll cover some of these issues in this chapter.
Invaluable as GDB, DDD, and Eclipse are, they can't help you if your program doesn't even compile. In this section we'll give you some tips on dealing with this situation.
Sometimes the compiler will tell you you've got a syntax error in line x, when in fact line x is perfectly correct, and the real error is in an earlier line.
For example, here is the source file bintree.c from Chapter 3, with a syntax error thrown in at a point which we will not disclose yet (well, it's fairly obvious if you want to look for it).
1 // bintree.c: routines to do insert and sorted print of a binary tree 2 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 struct node { 7 int val; // stored value 8 struct node *left; // ptr to smaller child 9 struct node *right; // ptr to larger child 10 }; 11 12 typedef struct node *nsp; 13 14 nsp root; 15 16 nsp makenode(int x) 17 { 18 nsp tmp; 19 20 tmp = (nsp) malloc(sizeof(struct node)); 21 tmp->val = x; 22 tmp->left = tmp->right = 0; 23 return tmp; 24 } 25 26 void insert(nsp *btp, int x) 27 { 28 nsp tmp = *btp; 29 30 if (*btp == 0) { 31 *btp = makenode(x); 32 return; 33 } 34 35 while (1) 36 { 37 if (x > tmp-<val) { 38 39 if (tmp->left != 0) { 40 tmp = tmp->left; 41 } else { 42 tmp->left = makenode(x); 43 break; 44 } 45 46 } else { 47 48 if (tmp->right != 0) { 49 tmp = tmp->right; 50 } else { 51 tmp->right = makenode(x); 52 break; 53 } 54 55 } 56 } 57 58 void printtree(nsp bt) 59 { 60 if (bt == 0) return; 61 printtree(bt->left); 62 printf("%d\n",bt->val); 63 printtree(bt->right); 64 } 65 66 int main(int argc, char *argv[]) 67 { 68 int i; 69 70 root = 0; 71 for (i = 1; i > argc; i++) 72 insert(&root, atoi(argv[i])); 73 printtree(root); 74 }
Running this through GCC produces
$ gcc -g bintree.c bintree.c: In function `insert': bintree.c:75: parse error at end of input
Since line 74 is the end of the source file, the second error message is rather uninformative, to say the least. But the first message suggests that the problem is in insert()
, so that's a clue, even though it doesn't say what or where the problem is.
In this kind of situation, the typical culprit is a missing closing brace or semicolon. You could check for this directly, but in a large source file this may be difficult. Let's take a different approach.
Recall the Principle of Confirmation from Chapter 1. Here, let's first confirm that the problem really is in insert()
. To do so, temporarily comment out that function from the source code:
... tmp->val = x; tmp->left = tmp->right = 0; return tmp; } // void insert(nsp *btp, int x) // { // nsp tmp = *btp; // // if (*btp == 0) { // *btp = makenode(x); // return; // } // // while (1) // { // if (x < tmp->val) { // // if (tmp->left != 0) { // tmp = tmp->left; // } else { // tmp->left = makenode(x); // break; // } // // } else { // // if (tmp->right != 0) { // tmp = tmp->right; // } else { // tmp->right = makenode(x); // break; // } // // } // } void printtree(nsp bt) { if (bt == 0) return; ...
Preferably, you will use a quick shortcut to do the commenting out, such as a block operation. Text editor shortcuts that are useful in debugging contexts will be discussed in Chapter 7.
Save the file, and then re-run GCC:
$ gcc -g bintree.c /tmp/ccg0LDCS.o: In function `main': /home/matloff/public_html/matloff/public_html/Debug/Book/DDD/bintree.c:72: undefined reference to 'insert' collect2: ld returned 1 exit status
Don't be distracted by the fact that the linker, LD, complained that it couldn't find insert()
. After all, you knew that was coming, since you commented out that function. Instead, the point of interest is that there is no complaint of a syntax error, as you had before. So, you have indeed confirmed that the syntax error is somewhere in insert()
. Now, uncomment the lines of that function (again, preferably using a text editor shortcut such as "undo") and save the file. Also, just to make sure you've restored things correctly, re-run GCC to confirm that the syntax error resurfaces (not shown here).
At this point you can apply another principle stated in Chapter 1: the Principle of Binary Search. Repeatedly narrow down your search area in the function insert()
, cutting the area in half each time, until you obtain a sufficiently small area in which to spot the syntax error.
To that end, first comment out approximately half of the function. A reasonable way to do that would be to simply comment out the while
loop. Then re-run GCC:
$ gcc -g bintree.c $
Aha! The error message disappeared, so the syntax problem must be somewhere within the loop. So, you've narrowed down the problem to that half of the function, and now you'll cut that area in half, too. To do so, comment out the else
code:
void insert(nsp *btp, int x) { nsp tmp = *btp; if (*btp == 0) { *btp = makenode(x); return; } while (1) { if (x < tmp->val) { if (tmp->left != 0) { tmp = tmp->left; } else { tmp->left = makenode(x); break; } } // else { // // if (tmp->right != 0) { // tmp = tmp->right; // } else { // tmp->right = makenode(x); // break; // } // // } }
Re-running GCC, you'll find that the problem reappears:
$ gcc -g bintree.c bintree.c: In function `insert': bintree.c:75: parse error at end of input
So, the syntax error is either in the if
block or at the end of the function. By this time, you've narrowed the problem down to only seven lines of code, so you should probably be able to find the problem by visual inspection; it turns out that we had accidentally omitted the closing brace in the outer if-then-else.
The Principle of Binary Search can be very helpful in finding syntax errors of unknown locations. But in temporarily commenting out code, be sure not to create new syntax errors of your own! Comment out an entire function, an entire loop, and so on, as we did here.
Sometimes GCC—actually LD, the linker, which is invoked by GCC during the process of building your program—will inform you that it cannot find one or more functions called by your code. This is typically due to failure to inform GCC of the location of a function library. Many, if not most, readers of this book will be well versed on this topic, but for those who are not, we will provide a short introduction in this section. Note that our discussion here applies mainly to Linux and to various degrees to other Unix-family operating systems.
Let's use the following very simple code as an example, consisting of a main program, in a.c,
// a.c int f(int x); main() { int v; scanf("%d",&v); printf("%d\n",f(v)); }
and a subprogram, in z/b.c:
// b.c int f(int x) { return x*x; }
If you try to compile a.c without any attempt to link in the code in b.c, then LD will of course complain:
$ gcc -g a.c /tmp/ccIP5WHu.o: In function `main': /debug/a.c:9: undefined reference to `f' collect2: ld returned 1 exit status
We could go to z, compile b.c and then link in the object file:
$ cd z $ gcc -g -c b.c $ cd .. $ gcc -g a.c z/b.o
However, if you had a lot of functions to link in, possibly from different source files, and if these functions were likely to be useful for future programs you might write, you could create a library, a single archive file. There are two kinds of library files. When you compile code that calls functions in a static library, those functions become part of the resulting executable file. On the other hand, if the library is dynamic, the functions are not physically attached to the calling code until the program is actually executed.
Here is how you could create a static library, say lib88.a, for the example here.
On Unix systems, it's customary to give static library filenames the suffix .a, with a standing for archive. Also, the custom is to give any library a name starting with lib.
$ gcc -g -c b.c $ ar rc lib88.a b.o
The ar
command here creates the library lib88.a from whatever functions it finds in the file b.o. You would then compile your main program:
$ gcc -g a.c -l88 -Lz
The -l
option here is a shortcut, having the same effect as
$ gcc -g a.c lib88.a -Lz
which directs GCC to tell LD that it will need to find functions in the library lib88.a (or a dynamic variant, as you'll see below).
The -L
option directs GCC to tell LD to look in directories other than the current one (and the default search directories) when looking for your functions. In this case, it says that z is such a directory.
The disadvantage of this approach is that if many programs are using the same library, they each will contain space-wasting separate copies of it on disk. This problem is solved (at the expense of a little extra load time) by using dynamic libraries.
In the example here, you'd use GCC directly to create a dynamic library, rather than using ar. In z you would run
$ gcc -fPIC -c b.c $ gcc -shared -o lib88.so b.o
This creates the dynamic library lib88.so. (Unix custom is to use the suffix .so, shared object, possibly followed by a version number, for naming dynamic libraries.) Link to it as you did for the static case:
$ gcc -g a.c -l88 -Lz
However, it now works a little differently. Whereas in the static case the functions called from the library would become part of our executable file a.out, now a.out will merely contain a notation that this program makes use of the library lib88.so. And significantly, that notation will not even state where that library is located. The only reason GCC (again, actually LD) wanted to take a peek at lib88.so at compile time was to get information about the library that it needs for the link.
The link itself will occur at run time. The operating system will search for lib88.so, and then link it into your program. That brings up the question of where the OS performs this search.
First of all, let's use the ldd
command to check which libraries the program needs, and where, if anywhere, the OS finds them:
$ ldd a.out lib88.so => not found libc.so.6 => /lib/tls/libc.so.6 (0x006cd000) /lib/ld-linux.so.2 (0x006b0000)
The program needs the C library, which it finds in the directory /lib/tls, but the OS fails to find lib88.so. The latter is in the directory /Debug/z, but that directory is not part of the OS's normal search path.
One way to fix that would be to add /Debug/z to that search path:
% setenv LD_LIBRARY_PATH ${LD_LIBRARY_PATH}:/Debug/z
If you want to add several directories, string their names together with colons as delimiters. (This is for the C shell or TC shell.) For bash
, issue the commands
$ LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/Debug/z $ export LD_LIBRARY_PATH
Let's make sure it works:
$ ldd a.out lib88.so => /Debug/z/lib88.so (0xf6ffe000) libc.so.6 => /lib/tls/libc.so.6 (0x006cd000) /lib/ld-linux.so.2 (0x006b0000)
There are various other approaches, but they are beyond the scope of this book.
Open source software has become quite popular, especially among Linux users. A problem that sometimes arises, though, is that the build script, typically named configure, that comes with the source code cannot find some necessary libraries. Attempts to solve the problem by setting the ld_library_path
environment variable may fail. As this falls under the "missing libraries" topic under discussion here, and it is usually not documented in the source packages, a brief note about it may be of value.
Often, the root of the problem will lie in a program invoked by configure, named pkgconfig. The latter will retrieve information about libraries from certain metadata files, which have the suffix .pc, with the prefix being the name of the library. For example, the file libgcj.pc would contain the location of the library files libgcj.so*.
The default directory that pkgconfig searches for the .pc files depends on the location of pkgconfig itself. For instance, if the program resides in /usr/bin, it will search /usr/lib. That won't be enough if the needed library is /usr/local/lib. To fix that problem, set the environment variable pkg_config\_path
. In the C or TC shell, you would issue the shell command
% setenv PKG_CONFIG_PATH /usr/lib/pkgconfig:/usr/local/lib/pkgconfig