Question
I am compiling this POSIX threads example in C on Ubuntu Linux:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 5
void *PrintHello(void *threadid)
{
long tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc;
long t;
for (t = 0; t < NUM_THREADS; t++) {
printf("In main: creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc) {
printf("ERROR: return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
But when I compile it with:
gcc -o term term.c
I get this error:
warning: incompatible implicit declaration of built-in function ‘exit’
undefined reference to `pthread_create`
collect2: ld returned 1 exit status
Why does this happen even though pthread.h is included, and how should I compile this program correctly on Linux?
Short Answer
By the end of this page, you will understand the difference between including a header file and linking a library, why pthread_create can produce an "undefined reference" error, and how to compile POSIX thread programs correctly in C on Linux using gcc and the -pthread flag.
Concept
In C, #include and linking solve two different problems:
#include <pthread.h>gives the compiler the declaration of functions likepthread_create.- Linking tells the system where the actual compiled function implementation lives.
That is why including pthread.h is not enough by itself.
What "undefined reference" means
When GCC compiles your source file, it first checks syntax and function declarations. Because pthread.h is included, the compiler knows that pthread_create exists and what arguments it expects.
But after compilation, the linker tries to build the final executable. At that stage, it must find the compiled implementation of pthread_create inside the pthread library. If you do not link that library, the linker reports:
undefined reference to `pthread_create`
Why -pthread matters
On Linux, POSIX thread programs are usually compiled with:
gcc file.c -pthread
The -pthread option is important because it does two things:
- enables thread-related compiler settings
- links the pthread library correctly
Many beginners try -lpthread, which may work in some cases, but -pthread is the recommended and more complete option.
Why you also saw the exit warning
Your program calls exit(-1), but exit is declared in:
#include <stdlib.h>
Without that header, the compiler warns that exit was used without a proper declaration.
So there are really two issues in the original program:
- Missing
#include <stdlib.h>forexit - Missing
-pthreadduring compilation/linking
Mental Model
Think of C compilation like building a movie set:
- A header file is the script telling actors what lines exist.
- A library is the actual actor showing up on set.
Including pthread.h means your code has read the script and knows pthread_create is a real function.
But if you do not link pthreads, the actor never arrives. When the linker tries to assemble the final program, it says:
- "I know this character name exists..."
- "...but I cannot find the actual person to play it."
That is the meaning of undefined reference.
Syntax and Examples
The normal way to compile a pthread program with GCC on Linux is:
gcc program.c -o program -pthread
Corrected example
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 5
void *print_hello(void *threadid)
{
long tid = (long)threadid;
printf("Hello World! It's me, thread #%ld!\n", tid);
return NULL;
}
int main(void)
{
pthread_t threads[NUM_THREADS];
int rc;
for (long t = 0; t < NUM_THREADS; t++) {
printf("In main: creating thread %ld\n", t);
rc = pthread_create(&threads[t], NULL, print_hello, (void *)t);
if (rc != 0) {
printf("ERROR: return code from pthread_create() is %d\n", rc);
exit(EXIT_FAILURE);
}
}
pthread_exit(NULL);
}
Compile it with:
gcc term.c -o term -pthread
What each part does
#include <pthread.h>declares thread functions and types.pthread_tstores thread handles.pthread_create(...)starts a new thread.pthread_exit(NULL)keeps the process alive until threads finish.-pthreadtells GCC to compile and link for POSIX threads.
Alternative
You may also see:
gcc term.c -o term -lpthread
This can link the pthread library, but -pthread is the preferred option on Linux because it also sets compiler options appropriately.
Step by Step Execution
Consider this smaller example:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
void *worker(void *arg)
{
printf("Worker thread running\n");
return NULL;
}
int main(void)
{
pthread_t thread;
if (pthread_create(&thread, NULL, worker, NULL) != 0) {
printf("Could not create thread\n");
exit(EXIT_FAILURE);
}
pthread_join(thread, NULL);
printf("Main thread finished\n");
return 0;
}
Compile:
gcc main.c -o main -pthread
Step by step
-
The compiler reads
#include <pthread.h>.- It learns the declaration of
pthread_createandpthread_join.
- It learns the declaration of
-
The compiler compiles the source into object code.
- Calls to
pthread_createremain as references to external library code.
- Calls to
-
The linker runs.
- Because
-pthreadwas provided, GCC links the pthread library. - The linker finds the real implementation of
pthread_create.
- Because
-
The executable is created successfully.
-
At runtime,
main()callspthread_create.- A new thread starts and runs
worker.
- A new thread starts and runs
-
pthread_join(thread, NULL)waits for that thread to finish. -
The program prints:
Worker thread running
Main thread finished
What happens without -pthread
Steps 1 and 2 still work.
The failure happens in step 3, when the linker cannot find the real implementation of pthread_create. That is why the error is a linker error, not a syntax error.
Real World Use Cases
POSIX threads are used in many kinds of Linux C programs.
Common uses
- Servers: handle multiple client connections at once
- Background workers: process jobs while the main program keeps responding
- File processing: read, transform, or compress multiple files in parallel
- Monitoring tools: one thread collects metrics while another handles output
- GUI or event-driven apps: keep the interface responsive while work runs in the background
Example scenarios
Web server
A thread can handle each request or pick tasks from a shared queue.
Log processing tool
One thread reads logs, another parses them, and another writes summaries.
Download manager
Multiple threads can download different files at the same time.
In all of these, simply including pthread.h is still not enough. The final executable must be linked with pthread support.
Real Codebase Usage
In real projects, developers rarely stop at just calling pthread_create. They also use patterns that make threaded code safer and easier to maintain.
Common patterns
Error checking
Always check the return value of pthread functions.
int rc = pthread_create(&thread, NULL, worker, NULL);
if (rc != 0) {
fprintf(stderr, "pthread_create failed: %d\n", rc);
return EXIT_FAILURE;
}
Joining threads
If the main thread exits too early, worker threads may not finish. pthread_join is often clearer than calling pthread_exit in main.
pthread_join(thread, NULL);
Passing structured data
In real code, developers usually pass a pointer to a struct instead of casting integers to void *.
typedef struct {
int id;
const char *name;
} Task;
Guard clauses
Thread creation often appears behind validation or setup checks.
if (config.thread_count <= 0) {
return EXIT_FAILURE;
}
Build system integration
In Makefiles or CMake projects, -pthread is added to compiler and linker settings so every relevant target builds correctly.
Practical advice
If a real codebase uses pthreads, treat -pthread as part of the build configuration, not something to remember manually each time.
Common Mistakes
1. Including the header but forgetting to link the library
This is the exact problem from the question.
Broken
gcc term.c -o term
Correct
gcc term.c -o term -pthread
2. Forgetting the correct header for exit
Broken
#include <stdio.h>
int main(void) {
exit(1);
}
This can trigger a warning because exit is declared in <stdlib.h>.
Correct
#include <stdio.h>
#include <stdlib.h>
3. Using -lpthread when -pthread is the better choice
-lpthread may link successfully on some systems, but -pthread is the safer and recommended option with GCC on Linux.
4. Passing integers through void * carelessly
This appears in many simple tutorials:
pthread_create(&threads[t], NULL, worker, (void *)t);
It may work for demonstration code, but in production code it is better to pass a pointer to real data.
Better approach
typedef struct {
long id;
} ThreadData;
5. Not waiting for threads to finish
If main returns immediately, your process can end before worker threads complete.
Better options
- use
pthread_joinfor each thread - or use
pthread_exit(NULL)inmainwhen appropriate
6. Confusing compile-time and link-time errors
- Compile-time: syntax errors, wrong types, missing declarations
- Link-time: missing library implementations like
pthread_create
This distinction helps diagnose errors much faster.
Comparisons
| Concept | What it does | Example problem | Typical fix |
|---|---|---|---|
| Header include | Gives declarations to the compiler | implicit declaration of function | Add the right #include |
| Linking a library | Provides compiled implementations | undefined reference to pthread_create | Add -pthread |
-lpthread | Links pthread library only | May work, but less complete | Prefer -pthread |
-pthread | Sets thread options and links pthreads | Recommended for GCC on Linux | Use for compile and link |
pthread_exit vs return in main
| Approach | Effect |
|---|---|
return 0; from main | Process exits; other threads may be terminated if still running |
pthread_exit(NULL); in main | Main thread ends, process stays alive until other threads finish |
pthread_join(...) | Waits for specific thread(s) and is often clearer |
Declaration vs definition
| Term | Meaning |
|---|---|
| Declaration | Tells the compiler a function exists |
| Definition/implementation | The actual compiled code for the function |
pthread.h provides declarations. The pthread library provides implementations.
Cheat Sheet
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
Compile command
gcc file.c -o file -pthread
Key rule
- Header files declare functions.
- Libraries provide implementations.
undefined referenceusually means a linking problem.
Common pthread functions
pthread_create(&thread, NULL, worker, arg);
pthread_join(thread, NULL);
pthread_exit(NULL);
If you see this error
undefined reference to `pthread_create`
Check:
- Did you compile with
-pthread? - Are you using
gcc source.c -o app -pthread? - Did you spell the function correctly?
- Are you actually on a system with pthread support?
If you see this warning
implicit declaration of function 'exit'
Add:
#include <stdlib.h>
Best practice
- Prefer
-pthreadover-lpthread - Check return values
- Use
pthread_joinwhen you need to wait for threads - Pass structs or allocated data to threads in real programs
FAQ
Why does pthread.h not solve the error by itself?
Because headers only declare functions. The linker still needs the actual pthread library implementation.
What is the correct GCC command for pthreads on Linux?
Usually:
gcc program.c -o program -pthread
Is -lpthread the same as -pthread?
Not exactly. -lpthread mainly links the library. -pthread is the recommended option because it also applies thread-related compiler settings.
Why did I get a warning about exit?
Because exit is declared in <stdlib.h>, and that header was missing.
Is pthread_create part of standard C?
No. It is part of POSIX threads, commonly available on Unix-like systems such as Linux.
Should I use pthread_exit(NULL) or pthread_join()?
For simple demos, pthread_exit(NULL) in main can keep the process alive. In real programs, pthread_join() is often clearer because it explicitly waits for threads.
Can this error happen with other libraries too?
Yes. Any time you include a header but forget to link its library, you can get an undefined reference error.
Mini Project
Description
Build a small C program that creates several worker threads to process numbered tasks. This project demonstrates the full workflow: including the correct headers, compiling with pthread support, creating threads safely, and waiting for them to finish.
Goal
Create and run a multi-threaded C program on Linux without linker errors, using pthread_create and pthread_join correctly.
Requirements
- Create a C program that starts 3 worker threads.
- Each thread should print its own task ID.
- The main thread must wait for all worker threads to finish.
- Include all required headers.
- Compile the program with the correct GCC pthread flag.
Keep learning
Related questions
2D Array Loop Order and Cache Performance in C
Learn why swapping nested loops changes 2D array performance in C, using row-major memory layout, cache locality, and practical benchmarks.
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.