Every C programmer meets the same short list of mistakes, usually in the first week and occasionally a decade later. What makes them worth cataloguing is that most produce error messages that do not describe them - or, worse, no message at all.
Each entry below is a symptom, the cause behind it, and the fix.
Missing Semicolon and the Cascade
Symptom: a wall of errors, all reported on lines that look correct.
program.c:6:5: error: expected ';' before 'printf'
program.c:7:5: error: expected declaration specifiers before 'return'
Cause: C ends statements with ;. Leave one out and the compiler glues the next line onto the current one, then reports confusion at the point where the combined text stops making sense - typically the following line.
int main(void) {
int x = 5 /* missing semicolon */
printf("%d\n", x);
return 0;
}
Fix: when a batch of errors appears, fix only the first one and recompile. Everything after it may be fallout. And always check the line before the one the compiler names.
The same cascade comes from an unclosed brace or an unterminated /* comment, where the errors can land dozens of lines away.
One extra note: no semicolon after an if, for, while header, or a function definition's closing brace. if (x > 0); compiles fine and does nothing - the ; is the whole body.
Assignment Instead of Comparison
Symptom: a condition that is always true, or a variable that mysteriously changes.
int x = 5;
if (x = 10) { /* assigns 10, then tests 10 -> true */
printf("x is ten\n"); /* always prints; x is now 10 */
}
Cause: = assigns, == compares. The assignment's value is the assigned value, so if (x = 10) tests 10, which is nonzero, which is true. The compiler accepts it because it is occasionally what people mean.
Fix: use == in every condition, and turn on -Wall so the compiler warns:
warning: suggest parentheses around assignment used as truth value
If you genuinely mean assignment inside a condition - common with while ((c = getchar()) != EOF) - the extra parentheses say so and silence the warning.
Implicit Declaration of a Function
Symptom:
warning: implicit declaration of function 'printf'
warning: implicit declaration of function 'malloc'
...followed sometimes by strange runtime results or a link error.
Cause: the compiler met a call to a function it has no declaration for. In pre-C99 dialects it assumed the function returned int and carried on; on 64-bit systems that assumption truncates a returned pointer to 32 bits, which is how a missing <stdlib.h> turns malloc into a crash.
Fix: include the right header.
| Function | Header |
|---|---|
printf, scanf, fopen | <stdio.h> |
malloc, free, atoi, rand, exit | <stdlib.h> |
strlen, strcpy, strcmp | <string.h> |
sqrt, pow, fabs | <math.h> |
isdigit, toupper | <ctype.h> |
time | <time.h> |
For your own functions, the same warning means you called one before defining it. Put a prototype above main:
scanf Without the &
Symptom: the program crashes on input, or reads nothing and leaves the variable unchanged.
int age;
scanf("%d", age); /* missing & : passes the value, not the address */
Cause: scanf writes into your variable, so it needs the variable's address. Passing age hands over whatever garbage was in it and scanf treats that as a place to write, which is usually a segfault.
Fix: & before the variable - for every type except an array, which is already an address:
Two adjacent traps in the same family: use %lf for a double in scanf (%f there means float, and writing a float's worth of bytes into a double leaves it wrong), and always bound a %s with a width - %49s for a 50-byte buffer - or a long word overruns it.
Better still: read a whole line with fgets and parse it, which cannot overflow and does not leave stray input in the buffer. More on both in scanf.
Comparing Strings with ==
Symptom: two strings that clearly match compare as different.
char a[] = "hello";
char b[] = "hello";
if (a == b) { /* comparing two addresses: false */
printf("same\n");
}
Cause: a C string is not a value, it is a pointer to the first character. == compares the pointers. Two arrays holding identical text live at different addresses, so the test is false. (Confusingly, comparing two identical literals sometimes gives true, because the compiler may store one copy - which makes the bug intermittent.)
Fix: strcmp, and remember it returns 0 for equal:
The inverted reading catches people too: if (strcmp(a, b)) is true when the strings differ, since a nonzero result means "not equal". Always write the == 0 explicitly.
Integer Division
Symptom: an average of 0, a percentage that is always 0 or 100, a ratio that lost its fraction.
int correct = 7, total = 10;
double score = correct / total; /* 0.0, not 0.7 */
Cause: both operands are int, so C does integer division and truncates before the result is assigned to a double. 7 / 10 is 0; converting 0 to double is 0.0.
Fix: make one operand floating point before the division:
Casting one operand promotes the other automatically. Casting the result is too late - the truncation already happened. See type casting.
The same trap hides in expressions like (a + b) / 2 for a midpoint and 1 / 2 * x, which is always 0 regardless of x.
Missing or Wrong return
Symptom: a function returns a plausible-looking wrong number, different each run or each build.
int add(int a, int b) {
int sum = a + b;
/* no return statement */
}
Cause: reaching the end of a non-void function without returning gives an unspecified value - in practice whatever happened to be in the return register. It is undefined behavior if the caller uses it.
The sneakier form returns on some paths and not others:
int classify(int n) {
if (n > 0) return 1;
if (n < 0) return -1;
/* n == 0 falls off the end */
}
Fix: return on every path, and compile with -Wall - GCC's "control reaches end of non-void function" catches both versions.
Uninitialized Variables
Symptom: garbage output, or results that change between runs and between optimization levels.
int total; /* contains whatever was on the stack */
for (int i = 1; i <= 5; i++) {
total += i; /* adding to garbage */
}
printf("%d\n", total); /* some enormous number */
Cause: local variables are not zeroed. A global or static variable is set to zero automatically; a local one starts as whatever bytes were already at that stack address.
Fix: initialize at the point of declaration. It costs nothing and removes the whole class of bug:
-Wall -Wextra warns about many of these ("may be used uninitialized"), and -fsanitize=memory or valgrind catches the rest. This one is especially nasty because an uninitialized pointer leads straight to a segmentation fault.
Off-by-One
Symptom: the last item is missed, or one element too many is touched and the program misbehaves later.
int arr[5];
for (int i = 0; i <= 5; i++) { /* touches arr[5], which does not exist */
arr[i] = i;
}
Cause: an n-element array has indices 0 through n - 1. <= runs one extra pass.
Fix: the i < n pattern, and compute n from the array rather than writing the number twice:
The string version - forgetting room for '\0' - is the same error wearing a different hat, and char word[5] with "hello" in it is a buffer overrun.
Two Smaller Ones Worth Knowing
Semicolon after a loop header. for (int i = 0; i < 10; i++); followed by a braced block runs the loop ten times doing nothing, then runs the block once. It compiles cleanly.
sizeof on a pointer. Inside a function, an array parameter is a pointer, so sizeof(arr) is the pointer's size (8 bytes), not the array's. Pass the length as a separate argument:
The Habit That Prevents Most of This
Compile with warnings on, from the very first program:
gcc -Wall -Wextra -g program.c -o program
-Wall -Wextra catches the assignment-in-condition, the missing return, the uninitialized read, the unused variable, and the printf specifier that does not match its argument. Adding -fsanitize=address,undefined during development catches nearly all of the rest at the moment they happen.
Treat every warning as an error you have not hit yet. A C program that compiles with no warnings is not guaranteed correct - but almost every C program that crashes was warning about something first.
Frequently Asked Questions
What does 'implicit declaration of function' mean in C?
The compiler met a call to a function it has never seen declared. Almost always you forgot an #include - printf needs <stdio.h>, malloc needs <stdlib.h>, strlen needs <string.h>. It can also mean you called your own function before defining it, which a prototype above main fixes.
Why does my C program report an error on a line that looks fine?
Usually because the real mistake is on the line before. A missing semicolon, an unclosed brace, or an unterminated comment makes the compiler read your two lines as one, so it reports the confusion where it finally becomes unparseable. Always check the line above the reported one first.
Why can't I compare strings with == in C?
Because a C string is a char * - a pointer - so == compares two addresses, not the characters they point at. Two identical strings stored in different places give false. Use strcmp(a, b) == 0 from <string.h>, which returns 0 when the contents match.
Why does 5 / 2 give 2 in C?
Because both operands are integers, so C performs integer division and discards the fraction. Make one side a floating-point value to get 2.5: 5.0 / 2, or (double)a / b when working with variables. Casting the result is too late - (double)(5 / 2) is 2.0.