To get on this list, a bug has to be able to cause at least half a day of futile head scratching, and has to be aggravated by the poor design of the "C" language. In the interests of equal time, and to see how the world has progressed in the 20-odd years since "C" escaped from it's spawning ground, see my Top 10 Ways to be Screwed by the Java programming language.
A better language would allow fallible programmers to be more productive. Infallible programmers, of the type unix' and "C" designers anticipated, need read no further.
a=b; /* this is a bug c=d; /* c=d will never happen */
if(a=b) c; /* a always equals b */Depending on your viewpont, the bug in the language is that the assignment operator is too easy to confuse with the equality operator; or maybe the bug is that C doesn't much care what constitutes a boolean expression: (a=b) is not a boolean expression! (but C doesn't care)
#define assign(a,b) a=(char)b assign(x,y>>8)becomes
Mismatched header files
Suppose foo.h contains:
struct foo { BOOL a};
file F1.c contains
#define BOOL char
#include "foo.h"
file F2.c contains
#define BOOL int
#include "foo.h"
now, F2. and F2 disagree about the fundamental attributes of structure
"foo". If they talk to each other, You Lose!
Suppose you write this
int foo (a)
{ if (a) return(1); } /* buggy, because sometimes no value is returned */
Generally speaking, C compilers, and C runtimes either can't or don't tell you there is anything wrong. What actually happens depends on the particular C compiler and what trash happened to be left lying around wherever the caller is going to look for the returned value. Depending on how "lucky" you are, the program may even appear to work for a while.
Now, imagine the havoc that can ensue if "foo" was thought to return a
pointer!
Consider this bit-packing struct:
struct eeh_type
{
uint16 size: 10; /* 10 bits */
uint16 code: 6; /* 6 bits */
};
Depending on which C compiler, and which "endian" flavor of machine you
are on, this might actually be implemented as
<10-bits><6-bits>or as
<6-bits><10-bits>So what matters? If you are trying to match bits in a real-world file, everything!
foo(pointer->member, pointer = &buffer[0]);Works with gcc (and other compilers I used until I tried acc) and does not with acc. The reason is that gcc evaluates function arguments from left to right, while acc evaluates arguments from right to left.
K&R and ANSI/ISO C specifications do not define the order of evaluation for function arguments. It can be left-to-right, right-to-left or anything else and is "unspecified". Thus any code which relies on this order of evaluation is doomed to be non-portable, even across compilers on the same platform.
This isn't an entirely non-controversial point of view. Read the
supplementary
dialog on the subject.
if( ... ) foo(); else bar();which, when adding debugging statements, becomes
if( ... ) foo(); /* the importance of this semicolon can't be overstated */ else printf( "Calling bar()" ); /* oops! the else stops here */ bar(); /* oops! bar is always executed */There are a large class of similar errors, involving misplaced semicolons and brackets.
I once modified some code that called a function via a macro:
CALLIT(functionName,(arg1,arg2,arg3));CALLIT did more than just call the function. I didn't want to do the extra stuff so I removed the macro invocation, yielding:
functionName,(arg1,arg2,arg3);Oops. This does not call the function. It's a comma expression that:
switch (a) {
int var; /* this var isn't kosher. The compiler */
/* doesn't complain, but it sure screws things up! */
case A: ...
case B: ...
}
Still not convinced? Try this one (suggested by Mark Scarbrough <mes@triple-i.com>):
#define DEVICE_COUNT 4
static uint8 *szDevNames[DEVICE_COUNT] = {
"SelectSet 5000",
"SelectSet 7000"}; /* table has two entries of junk */
char *f()
{
char result[80];
sprintf(result,"anything will do");
return(result); /* Oops! result
is allocated on the stack. */
}
int g()
{
char *p;
p = f();
printf("f() returns: %s\n",p);
}
The "wonderful" thing about this bug is that it sometimes seems to
be a correct program; As long as nothing has reused the particular piece
of stack occupied by result.
Even within a single expression, even with only strictly manifest side effects, C doesn't define the order of the side effects. Therefore, depending on your compiler, I/++I might be either 0 or 1. Try this:
#includePrints either "Foo got 2", or "Bar got 2"int foo(int n) {printf("Foo got %d\n", n); return(0);} int bar(int n) {printf("Bar got %d\n", n); return(0);} int main(int argc, char *argv[]) { int m = 0; int (*(fun_array[3]))(); int i = 1; int ii = i/++i; printf("\ni/++i = %d, ",ii); fun_array[1] = foo; fun_array[2] = bar; (fun_array[++m])(++m); } Prints either i/++i = 1 or i/++i=0;
Actually, this bug is so well-known, it didn't even make the list! That doesn't make it less deadly when it strikes. Consider the simplest case:
void foo(a)
{ int b;
if(b) {/* bug! b is not initialized! */ }
}
and in truth, modern compilers will usually flag an error as blatant as
the above. However, you just have to be a little more clever to outsmart
the compiler. Consider:
void foo(int a)
{ BYTE *B;
if(a) B=Malloc(a);
if(B) { /* BUG! B may or may not be initialized */ *b=a; }
}
The compile-time environment of a typical compilation is cluttered with hundreds (or thousands!) of things that you typically have little or no awareness of. These things sometimes have dangerously common names, leading to accidents that can be virtually impossible to spot.
#include <stdio.h>
#define BUFFISZE 2048
long foo[BUFSIZ];
//note spelling of BUFSIZ != BUFFSIZE
This compiles without error, but will fail in predicably awful and mysterious
ways, becuase BUFSIZ is a symbol defined by stdio.h. A typo/braino
like this can be virtually impossible to find if the distance between the
the #define and the error is greater than in this trivial example.
You can use the free URL MINDER service to automatically notify you when this page (or any page on the web!) is updated. Very handy - saves all that boring checking in, only to discover that nothing is new.