Advertisement
Last updated: March 2025

Common C Compilation Errors: Syntax, Linker & Runtime — With Fixes

C's compiler messages are precise and informative — if you know how to read them. This guide covers every common error you'll encounter as a C programmer, organized by category: syntax errors (stopped by the compiler), linker errors (stopped at link time), compiler warnings (allowed but dangerous), and runtime errors (crash or wrong output when running). Each entry shows the exact error message, broken code, fixed code, and a clear explanation of the root cause.

How to Read a GCC Error Message

GCC error messages follow a consistent format. Once you can decode them, every error becomes actionable in seconds.

Advertisement
hello.c:4:5: error: expected ';' before 'return' ↑ filename ↑ line ↑ column ↑ severity ↑ what went wrong 4 | printf("Hello") | ^ | ; ↑ the actual source line with the caret pointing to the problem spot
  • Filename — which file contains the error
  • Line number — GCC's best guess at where to look (often one line after the actual mistake)
  • Column number — horizontal position on that line
  • Severityerror (stops build), warning (build continues), note (additional context)
  • Message — what the compiler detected
The off-by-one trap: GCC often reports an error on the line after the actual mistake. A missing semicolon on line 4 is usually reported on line 5. When an error message doesn't make sense at the reported line, always check the line above it first.
🔴
Syntax Errors
Detected by the compiler. GCC refuses to produce an executable until these are fixed.
error expected ';' before 'return'

Root cause: Every C statement must end with a semicolon. The compiler doesn't detect the missing semicolon until it sees the next token and realizes the statement was never closed — which is why the error points to the line after the mistake.

❌ broken
C
printf("Hello")  // ← missing ;
return 0;
✅ fixed
C
printf("Hello");  // ← semicolon added
return 0;
error expected '}' at end of input

Root cause: Every opening { must have a matching }. This error appears at the end of the file because the compiler reaches EOF while still waiting to close an open brace.

❌ broken
C
int main() {
    printf("Hi\n");
    return 0;
// ← missing closing }
✅ fixed
C
int main() {
    printf("Hi\n");
    return 0;
}  // ← closing brace added

Use an editor with bracket matching (VS Code, Vim with %) to spot mismatches instantly.

error 'x' undeclared (first use in this function)

Root cause: In C, every variable must be declared with its type before it can be used. Using a variable name that hasn't been declared — or that was declared in a different scope — produces this error.

❌ broken
C
int main() {
    x = 5;  // x not declared
    return 0;
}
✅ fixed
C
int main() {
    int x = 5;  // declare type first
    return 0;
}
error incompatible types when assigning to type 'int' from type 'char *'

Root cause: C is statically typed. Assigning a value of one type to a variable of an incompatible type is a compile-time error. Common triggers: assigning a string literal to an int, or passing the wrong pointer type to a function.

❌ broken
C
int age = "twenty-five";  // string → int
✅ fixed
C
int age = 25;          // integer literal
char *name = "Alice";  // string → char*
Advertisement
🟡
Linker Errors
Detected at link stage. The code compiled correctly but the linker can't resolve all symbol references.
linker error undefined reference to 'sqrt'

Root cause: You included <math.h> (which provides the declaration of sqrt) but didn't tell the linker to include the math library (libm) that contains the implementation. The -lm flag must come at the end of the GCC command, after the source files.

❌ broken command
Shell
gcc prog.c -o prog
undefined reference to 'sqrt'
✅ fixed command
Shell
gcc prog.c -o prog -lm
# -lm links libm (math library)
Rule: -l flags must come after the source/object files in a GCC command. gcc -lm prog.c -o prog can fail on some systems because the linker processes flags left-to-right and may not yet know it needs libm when it encounters the flag.
linker error undefined reference to 'my_function'

Root cause: Your project has multiple .c files but you only compiled one of them. The linker can see the call to my_function in main.o but it's not in any of the object files it was given.

❌ broken command
Shell
gcc main.c -o app
undefined reference to 'my_function'
✅ fixed command
Shell
gcc main.c utils.c -o app
# compile ALL .c files together
linker error undefined reference to 'greet'

Root cause: You wrote a function prototype (declaration) but never wrote the function body (definition). The compiler accepted the call because the prototype exists, but the linker can't find the actual code.

❌ broken
C
void greet();  // declaration only

int main() {
    greet();   // linker: where is greet()?
    return 0;
}
// greet body is missing!
✅ fixed
C
void greet();  // declaration

int main() {
    greet();
    return 0;
}

void greet() {         // definition added
    printf("Hello!\n");
}
Sponsored Discover Trending Offers & Deals Hand-picked promotions updated daily — one click opens a world of possibility. Explore Now →
🟣
Compiler Warnings
GCC still builds the executable — but these warnings almost always indicate real bugs. Treat them as errors.
Always compile with gcc -Wall -Wextra. These flags enable warnings that catch real bugs before they become runtime failures. Better still, use -Werror to treat all warnings as errors so your build fails until every warning is resolved. This is standard practice in professional C codebases.
warning implicit declaration of function 'printf'

Root cause: You called a function without the compiler having seen its declaration first. Usually means a missing #include. In C99 and later, implicit declarations are invalid — what used to be a warning is now effectively an error.

❌ broken
C
// #include <stdio.h> missing!
int main() {
    printf("Hi\n");
    return 0;
}
✅ fixed
C
#include <stdio.h>   // declares printf
int main() {
    printf("Hi\n");
    return 0;
}
warning unused variable 'count' [-Wunused-variable]

Root cause: A variable was declared but never used. This is almost always either dead code (a variable left over from a refactor) or a logic error (you intended to use the variable somewhere but forgot).

❌ broken
C
int main() {
    int count = 0;  // declared but never used
    printf("Done\n");
    return 0;
}
✅ fixed
C
int main() {
    // Remove unused variable, or use it:
    int count = 0;
    printf("Count: %d\n", count);
    return 0;
}
Advertisement
warning control reaches end of non-void function [-Wreturn-type]

Root cause: A function declared to return a value (e.g. int) has an execution path that reaches the end without a return statement. The returned value is undefined — the function returns garbage.

❌ broken
C
int add(int a, int b) {
    int result = a + b;
    // forgot: return result;
}
✅ fixed
C
int add(int a, int b) {
    int result = a + b;
    return result;  // return value added
}
warning format '%d' expects argument of type 'int', but argument 2 has type 'double'

Root cause: The format specifier in printf doesn't match the type of the argument. %d expects int, %f expects float/double, %s expects char*. A mismatch produces garbage output — or a crash on some platforms.

❌ broken
C
double pi = 3.14;
printf("%d\n", pi);  // %d ≠ double
✅ fixed
C
double pi = 3.14;
printf("%f\n", pi);  // %f for double
🟢
Runtime Errors
The program compiles and links successfully, but crashes or produces wrong output when running.
runtime Segmentation fault (core dumped)

Root cause: The program tried to read or write a memory address it doesn't have permission to access. The most common causes are: dereferencing a NULL pointer, accessing an array out of bounds, or using memory after it has been freed.

❌ broken — null pointer dereference
C
int *p = NULL;
*p = 42;   // segfault: writing to address 0
✅ fixed
C
int value = 0;
int *p = &value;   // point to valid memory
*p = 42;
diagnose with AddressSanitizerShell
gcc -g -fsanitize=address prog.c -o prog && ./prog
ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x... )
    #0 0x... in main prog.c:3
# AddressSanitizer tells you the exact line
runtime Segmentation fault (infinite recursion / stack overflow)

Root cause: A recursive function calls itself without ever reaching a base case. Each call adds a stack frame; eventually the stack runs out of space and the OS kills the process.

❌ broken
C
int factorial(int n) {
    // missing base case!
    return n * factorial(n - 1);
}
✅ fixed
C
int factorial(int n) {
    if (n <= 1) return 1;  // base case
    return n * factorial(n - 1);
}
runtime valgrind: LEAK SUMMARY — definitely lost: 40 bytes in 1 blocks

Root cause: Memory allocated with malloc() was never released with free(). The program doesn't crash — it silently consumes memory that is never returned to the OS. In long-running programs (servers, daemons), this eventually exhausts available memory.

❌ broken
C
int *arr = malloc(10 * sizeof(int));
arr[0] = 42;
// free(arr) missing — memory leak
✅ fixed
C
int *arr = malloc(10 * sizeof(int));
arr[0] = 42;
free(arr);   // always free what you malloc

Tools: -Wall, AddressSanitizer, and valgrind

The right tools catch different categories of bugs. Here is when to use each one:

recommended development workflowShell
# Step 1: Enable all warnings during compilation
gcc -Wall -Wextra -Werror -g prog.c -o prog

# Step 2: Catch memory errors at runtime with AddressSanitizer (fast)
gcc -Wall -g -fsanitize=address -fsanitize=undefined prog.c -o prog_asan
./prog_asan

# Step 3: Deep memory analysis with valgrind (slower, more thorough)
gcc -Wall -g prog.c -o prog
valgrind --leak-check=full ./prog
AddressSanitizer vs valgrind: AddressSanitizer (-fsanitize=address) is built into GCC/Clang, runs ~2x slower than normal, and catches buffer overflows, use-after-free, and stack overflows with precise line numbers. Valgrind is a separate tool, runs ~10–20x slower, but catches a broader set of memory issues including reads of uninitialized memory. Use ASan during daily development; valgrind for thorough pre-release checks.

Quick-Reference: Error → Cause → Fix

Error / Warning messageStageRoot causeFix
expected ';' before 'X'CompilerMissing semicolon on previous lineAdd ; to end of the line before the reported one
expected '}' at end of inputCompilerUnmatched opening brace {Add missing closing }
'X' undeclaredCompilerVariable used before declarationDeclare with type: int x;
implicit declaration of function 'X'CompilerMissing #includeAdd required header, e.g. #include <stdio.h>
format '%d' expects 'int', has 'double'Compilerprintf format specifier mismatchUse correct specifier: %f for double, %d for int
unused variable 'X'CompilerDeclared but never readRemove variable or use it
control reaches end of non-void functionCompilerMissing return statementAdd return value; before closing brace
undefined reference to 'sqrt'LinkerMath library not linkedAdd -lm at end of gcc command
undefined reference to 'my_func'LinkerSource file not compiled, or body missingCompile all .c files; write the function body
Segmentation faultRuntimeNULL pointer, out-of-bounds array, use-after-freeCompile with -g -fsanitize=address to get exact line
Memory leak (valgrind)Runtimemalloc without matching freeCall free(ptr) for every malloc()
Stack overflow / segfault in recursionRuntimeInfinite recursion — no base caseAdd a base case that stops the recursion
Advertisement

Frequently Asked Questions

A compiler error stops the build — GCC will not produce an executable and you must fix the error before proceeding. A compiler warning does not stop the build — GCC produces an executable, but it has detected something suspicious that is likely to cause bugs at runtime. Always treat warnings as errors using -Werror during development.
GCC reports errors where it first detects the problem, which is often one line after the actual mistake. A missing semicolon on line 4 is only detected when the compiler reaches the next statement on line 5 and sees an unexpected token. Always check the line immediately before the reported line when a syntax error doesn't make sense at the reported location.
Add -lm at the end of your GCC command: gcc prog.c -o prog -lm. The -lm flag links the math library (libm) which contains the implementation of sqrt(), pow(), sin(), and other math functions. Including <math.h> only provides declarations — -lm provides the actual compiled code.
Recompile with gcc -g -fsanitize=address prog.c -o prog and run again. AddressSanitizer will print the exact line and type of violation (NULL dereference, buffer overflow, use-after-free). Common root causes: dereferencing a pointer that is NULL or uninitialized, accessing an array beyond its bounds, or using memory after calling free() on it.
-Wall enables a set of "all common" warnings — unused variables, implicit function declarations, missing return values, always-true comparisons, and more. It does not actually enable all possible warnings (that would be -Weverything in Clang); it enables the set judged most useful without being noisy. Always pair it with -Wextra for additional useful warnings, and -Werror to make all warnings hard errors.
A compiler error means your C source code has a problem — bad syntax, wrong types, missing declarations. GCC catches these while reading and parsing .c files. A linker error means the C code compiled successfully (valid syntax and types) but the linker cannot find the implementation of something your code calls — typically because a library flag is missing or a source file wasn't compiled. "Undefined reference" messages are always linker errors.
Sponsored Ready to Explore More? Tap into trending offers and hand-picked deals — updated every day. Visit CoodeVerse →

Continue learning C on CoodeVerse

CoodeVerse Editorial Team

The CoodeVerse editorial team consists of experienced software developers and educators specializing in C, Python, Java, and web development. All content is technically reviewed and updated regularly.

Advertisement