Question
When should a function be declared with the extern keyword in C?
In my programs, the functions I call are declared in the header files I include. Why would I use extern to access a function that is not exposed by a header file? I may be misunderstanding how extern works.
Also, since a function declaration without extern is external by default, should extern be written explicitly in a header file?
Short Answer
By the end of this page, you will understand how extern declares names that are defined elsewhere, why function declarations normally do not need it, and why it is especially important for sharing global variables safely between C source files.
Concept
extern is a C storage-class specifier used in a declaration. It tells the compiler:
“This function or object exists, but its definition is provided elsewhere.”
C programs are commonly split across multiple .c source files. Each source file is compiled separately, then a linker combines the resulting object files. A declaration lets one source file compile code that refers to a name defined in another source file.
Functions
For ordinary functions, external linkage is the default. These declarations mean the same thing:
int add(int left, int right);
extern int add(int left, int right);
Both declare an externally linked function named add. Therefore, writing extern for a normal function declaration is valid but usually redundant.
A header should declare the function because every file that calls it needs its correct signature. A caller should not need to know which .c file contains the implementation.
/* math_utils.h */
int add(int left, int right);
/* math_utils.c */
#include "math_utils.h"
int add(int left, int right) {
return left + right;
}
/* main.c */
#include "math_utils.h"
int main(void) {
return add(2, 3);
}
Variables
extern matters much more for global variables. A global variable definition allocates storage:
int app_mode = 1; /* definition: one object is created */
Other source files can refer to that one object with an extern declaration:
extern int app_mode; /* declaration: storage is not allocated here */
The usual convention is:
- Put
externvariable declarations in a header. - Put exactly one variable definition in one
.cfile. - Include the header where the variable is used.
This prevents multiple-definition linker errors and keeps declarations consistent.
extern does not bypass encapsulation or make an undeclared function safely usable. You can manually write a declaration in a .c file, but doing so duplicates interface information and can cause bugs if it does not match the real definition. If another module intentionally does not expose a name in its public header, it is usually intended to remain private.
Mental Model
Think of a multi-file C program as several offices working on one project.
- A definition is the office where a resource is actually stored or a task is actually performed.
- A declaration is a directory entry saying the resource or task exists and describing how to use it.
externon a variable declaration says, “The real storage is in another office; use that shared one.”- Function declarations already imply, “The implementation may be in another office,” unless the function is declared
static.
A header file is the shared directory. It should describe the public functions and shared declarations, while .c files contain the implementations and private details.
Syntax and Examples
A declaration introduces a name and its type. A definition provides the function body or creates storage for an object.
Function declarations
/* These are equivalent for a normal function. */
int log_message(const char *message);
extern int log_message(const char *message);
The extern keyword is optional here because a function declaration has external linkage by default.
/* logger.c */
#include <stdio.h>
int log_message(const char *message) {
return printf("%s\n", message);
}
Global variable declaration and definition
/* settings.h */
#ifndef SETTINGS_H
#define SETTINGS_H
extern int verbose_mode;
#endif
/* settings.c */
#include "settings.h"
int verbose_mode = 0; /* The one definition */
/* main.c */
#include "settings.h"
int main(void) {
verbose_mode = 1;
return 0;
}
main.c does not create another verbose_mode; it uses the object defined in settings.c.
extern with an initializer
An extern declaration normally does not allocate storage:
extern int retry_limit;
However, an initializer makes it a definition:
extern int retry_limit = 3; /* Definition, not just a declaration */
Avoid placing such a definition in a header because every .c file that includes it may define the variable.
Step by Step Execution
Consider these three files.
/* counter.h */
extern int request_count;
void record_request(void);
/* counter.c */
#include "counter.h"
int request_count = 0;
void record_request(void) {
request_count++;
}
/* main.c */
#include <stdio.h>
#include "counter.h"
int main(void) {
record_request();
record_request();
printf("%d\n", request_count);
return 0;
}
Execution and build process:
- When compiling
main.c, the header tells the compiler thatrecord_requestis a function andrequest_countis anintdefined elsewhere. - When compiling
counter.c,int request_count = 0;creates the one global object and initializes it to0. - The linker connects
main.c's references torecord_requestandrequest_countwith their definitions incounter.c. maincallsrecord_request()twice.- Each call increments the same shared
request_countobject: first to1, then to2. printfprints2.
Compile the files together, for example:
cc -Wall -Wextra -std=c17 main.c counter.c -o counter_app
Real World Use Cases
- Program configuration: A small C program may define one global configuration object in
config.cand declare it withexterninconfig.h. - Embedded systems: Hardware register objects or board-level state can be declared in headers and defined in one hardware-specific source file.
- Metrics and counters: A diagnostic counter may be defined once and observed from multiple modules.
- Interoperating with libraries: Library headers declare external functions and, occasionally, external objects that are implemented in a compiled library.
- Large modular programs: Public headers provide declarations so files can call module APIs without depending on implementation source files.
For mutable global state, prefer passing state through function parameters or storing it in a dedicated context structure when practical. extern is useful, but widespread global state makes code harder to test and reason about.
Real Codebase Usage
In real C projects, extern is most often seen in header files for global variables, while it is usually omitted for function declarations.
Public API pattern
/* parser.h */
#ifndef PARSER_H
#define PARSER_H
int parse_document(const char *text);
#endif
/* parser.c */
#include "parser.h"
int parse_document(const char *text) {
if (text == NULL) {
return -1; /* Guard clause */
}
return 0;
}
The function declaration is public. The implementation can change without changing callers, as long as the declared interface stays compatible.
Shared variable pattern
/* app_state.h */
#ifndef APP_STATE_H
#define APP_STATE_H
typedef struct {
int debug_enabled;
} AppState;
extern AppState app_state;
#endif
/* app_state.c */
#include "app_state.h"
AppState app_state = {0};
Private helper pattern
/* parser.c */
static int is_space(char ch) {
return ch == ' ' || ch == '\t';
}
static gives this helper internal linkage: other .c files cannot refer to it. It should not be placed in the public header. This is usually preferable to leaving an internal helper externally visible.
For global state, a more maintainable API often exposes functions instead of the variable itself:
int app_is_debug_enabled(void);
void app_set_debug_enabled(int enabled);
That lets the module validate changes and later alter its internal representation.
Common Mistakes
Defining a global variable in a header
This is incorrect when the header is included by more than one .c file:
/* Incorrect: config.h */
int timeout_seconds = 30;
Each including source file can create a definition, producing a multiple-definition linker error.
Use a declaration in the header and one definition in a source file instead:
/* config.h */
extern int timeout_seconds;
/* config.c */
int timeout_seconds = 30;
Adding extern to a function without declaring the correct signature
/* Incorrect if the real function has parameters */
extern int calculate_total();
In C, calculate_total() means the parameters are unspecified; it does not mean “takes no parameters.” Use a full prototype:
int calculate_total(int price, int quantity);
For a function with no parameters, write void:
void initialize(void);
Using extern to access private implementation details
Manually declaring a non-public function in another .c file couples your code to an internal detail:
/* Fragile: do not duplicate a hidden module declaration. */
extern int internal_parse_token(const char *text);
Instead, use the module's public header and public API. If you own the module and the operation should be public, add the declaration to its intended header.
Forgetting the one definition
/* A declaration alone is not enough. */
extern int connection_count;
If no source file contains a definition such as int connection_count = 0;, the linker reports an undefined reference or unresolved external symbol.
Expecting extern to make a variable private
extern does the opposite of privacy: it declares an object with external linkage. Use static at file scope for a variable that should be private to one .c file:
static int cached_result;
Omitting include guards
Headers with declarations should use include guards to avoid repeated declarations and type-definition errors within one translation unit:
#ifndef EXAMPLE_H
#define EXAMPLE_H
extern int example_value;
#endif
Comparisons
| Declaration or definition | Meaning | Storage created? | Visible from other source files? |
|---|---|---|---|
int count; at file scope | Tentative definition of a global object | Yes, if no other definition appears in that translation unit | Usually yes |
extern int count; | Declaration of an object defined elsewhere | No | Yes |
int count = 0; | Definition of a global object | Yes | Yes |
static int count; | Definition with internal linkage | Yes | No |
int work(int x); | Function declaration with external linkage by default | No | Yes |
extern int work(int x); | Equivalent ordinary function declaration | No | Yes |
static int work(int x); | Function declaration/definition with internal linkage | Function code is provided only when defined | No |
extern versus static
- Use
externdeclarations when code in multiple translation units needs the same externally defined name. - Use
staticat file scope when a function or variable belongs only to one.cfile.
Header versus source file
| Location | Usually contains |
|---|---|
.h header | Function declarations, type declarations, macros, and extern declarations for shared variables |
.c source file | Function definitions, one definition of each shared global variable, and static private helpers |
A header is an interface description, not normally a place to allocate global storage.
Cheat Sheet
/* Function: extern is optional. */
int process(const char *input);
extern int process(const char *input); /* Same external linkage */
/* Shared global variable: extern declaration in a header. */
extern int error_count;
/* Exactly one definition in one .c file. */
int error_count = 0;
/* File-private name: use static. */
static int cache_size;
static void reset_cache(void);
externdeclares a name that is defined elsewhere.- A normal function declaration has external linkage by default;
externis usually redundant. - Put function prototypes in headers so callers know the correct parameter and return types.
- Put
externdeclarations for shared global variables in headers. - Define each external global variable exactly once in a
.cfile. extern int value = 1;is a definition because it has an initializer.staticat file scope makes a function or variable private to its.cfile.- Use
function(void)for no-parameter function prototypes, notfunction().
FAQ
Do I need extern before every C function declaration?
No. Function declarations have external linkage by default. Writing extern is allowed but normally unnecessary.
Why do global variables need extern in headers?
Without extern, a variable declaration in a header can become a definition in every source file that includes it. extern declares that one shared definition exists elsewhere.
Where should I define an extern variable?
Define it once in one .c file, usually the source file that owns that module. Include its header in that source file as well.
Does extern allocate memory for a variable?
Usually no. extern int value; is only a declaration. An initializer, such as extern int value = 10;, creates a definition and allocates storage.
Can I declare a function manually instead of including its header?
You can, but it is poor practice for public functions. A header provides one authoritative declaration and prevents mismatched prototypes across files.
Does extern make a function public?
A normal non-static function already has external linkage. Whether it is part of the supported public API is a project design decision, usually expressed by putting its declaration in a public header.
What linker error occurs if an extern variable is never defined?
The exact wording depends on the toolchain, but it is commonly an “undefined reference” or “unresolved external symbol” error.
Should I use global extern variables for application state?
Use them sparingly. They are sometimes appropriate, but passing a context structure or using accessor functions often makes dependencies clearer and testing easier.
Mini Project
Description
Create a small two-module request counter. One module owns a global counter, while another module reads and updates it through declarations in a header. This demonstrates the correct placement of an extern declaration and its single definition.
Goal
Build and run a program that records three requests and prints the shared request count.
Requirements
Create a header that declares a global request counter with extern.
Create one source file that defines the counter and provides a function that increments it.
Create a main source file that calls the increment function three times.
Print the final counter value from main.
Compile all source files into one executable.
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.