C Programming
C Programming
Error Handling in C
Last Updated : 6 Aug, 2025
In C programming, error handling is typically done using functions that
handle runtime errors, returning error codes or messages to notify the
programmer about the failure or incorrect operation.
Since C does not provide built-in exception handling like other high-
level languages (e.g., try-catch in Java or Python), error handling relies
heavily on function return values, global variables, and system calls.
A lot of C function calls return -1 or NULL or set an in case of an error
code as the global variable errno, so quick tests on these values are
easily done with an instance of ‘if statement’.
What is errno?
errno is a global variable defined in the <errno.h> header file that
indicates the error that occurred during a function call in C. When a
function fails, the errno variable is automatically set to a specific error
code, which helps identify the type of error encountered. Different values
of errno correspond to different types of errors, providing useful
information for error handling in C programs.
#include <errno.h>
#include <stdio.h>
int main() {
return 0;
}
Output
Value of errno: 2
Below is a list of a few different errno values and their corresponding
meaning:
errno
value Error
3 No such process
5 I/O error
10 No child processes
errno
value Error
11 Try again
12 Out of memory
13 Permission denied
1. Using if-else
#include <errno.h>
#include <stdio.h>
int main() {
FILE* fp;
if(fp == NULL){
printf("File openning error");
}else{
printf("File open successfully");
}
return 0;
}
Output
File openning error
Explanation: In the above program, we try to open a file in read mode
and use an if-else statement to check if the file was successfully opened.
If the file cannot be opened, the program will display a "File opening
error" message.
2. perror()
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(){
FILE* fp;
return 0;
}
Output
Value of errno: 2
Message from perror: No such file or directory
Explanation: This code attempts to open a non-existent file
using fopen(), which sets the errno variable with an error code. It then
prints the value of errno and uses perror() to display a descriptive error
message related to the failure, helping the user understand the cause of
the error, such as "No such file or directory."
3. strerror()
The strerror() function is also used to show the error description. This
function returns a pointer to the textual representation of the
current errno value.
Example
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main() {
FILE* fp;
return 0;
}
Output
Value of errno: 2
The error message is : No such file or directory
Explanation: The code attempts to open a non-existent file, sets errno on
failure, and then prints both the error code and the corresponding error
message using strerror().
4. ferror()
int main() {
FILE *fptr = fopen("[Link]", "w");
Output
Data written successfully.
Explanation: This code attempts to open a file and handles potential
errors using perror() if the file can't be opened. It reads the file character
by character, checks for errors during reading using ferror(), and prints an
appropriate message. The file is closed at the end, and the program exits
with a status indicating success or failure.
5. feof()
The feof() function checks whether the end of a file has been reached
during reading operations. It helps to identify when there is no more data
to read from the file.
Example
#include <stdio.h>
int main () {
FILE *fp = fopen("[Link]","r");
if (fp == NULL)
return 0;
do {
// Taking input single character at a time
char c = fgetc(fp);
printf("%c", c);
}while(1);
fclose(fp);
return(0);
}
[Link]
Welcome to GeeksforGeeks
Output
Welcome to GeeksforGeeks
Explanation: In this code, feof() is used to check if the end of the file
(EOF) has been reached while reading the file character by character
using fgetc(). If feof(fp) returns true, the while loop breaks, stopping
further reading of the file.
clearerr()
The clearerr() function is used to clear the error and EOF flags for a
stream. It allows recovery from errors and allows the stream to be reused
for further operations.
Example
#include <stdio.h>
int main() {
FILE *fptr = fopen("[Link]", "w+");
fprintf(fptr, "GeeksForGeeks!");
while (fgetc(fptr) != EOF);
if(feof(fptr)){
printf("EOF ancounter \n");
}
fclose(fptr);
return 0;
}
Output
EOF ancounter
Reset the EOF successfully
Explanation: The code opens a file in read mode, handles potential
errors during the file opening and operations, clears any error indicators
with clearerr(), and finally closes the file.
Exit Status
C programs use the exit() function to terminate the program and return a
status code to the operating system. The C standard specifies two
constants: EXIT_SUCCESS and EXIT_FAILURE, that may be passed to
exit() to indicate successful or unsuccessful termination, respectively.
These are macros defined in <stdlib.h> header file.
Example
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
FILE* fp;
if (fp == NULL) {
printf("Value of errno: %d\n", errno);
printf("Error opening the file: %s\n",
strerror(errno));
perror("Error printed by perror");
Output
Value of errno: 2
Error opening the file: No such file or directory
Error printed by perror: No such file or directory
Explanation: The code attempts to open a file using fopen(). If the file
doesn't exist, fopen() returns NULL, and the program prints the error
using errno, strerror(), and perror() to provide detailed information about
the failure. The program then exits with a failure status
using exit(EXIT_FAILURE). If the file is successfully opened, it is closed
and the program exits with a success status (exit(EXIT_SUCCESS)), but
no further code is executed after the exit() calls.
Error Handling without Predefined Methods
In the above discussion, we covered error handling using built-in methods,
but we can also handle some errors without relying on these methods,
such as division by zero, input validation, file opening errors, out-of-
range array access, and more.
Handling divide by zero errors is essential to avoid program crashes or
undefined behavior. You can check for a zero divisor before performing
division to prevent this error.
Example
#include <stdio.h>
int main() {
int num1 = 10, num2 = 0;
if (num2 == 0)
printf("Error: Division by zero is not allowed\n");
else
printf("Result: %d", num1 / num2);
return 0;
}
Output
Error: Division by zero is not allowed
Explanation: The program checks if the divisor num2 is zero before
performing the division. If it is, it prints an error message instead of
performing the division.
Exceptions are runtime anomalies or abnormal conditions that a program
encounters during its execution. C doesn’t provide any specialized
functionality for this purpose like other programming languages such as
C++ or Java. However, In C, goto keyword is often used for the same
purpose. The goto statement can be used to jump from anywhere to
anywhere within a function.
Let's take a look at an example:
#include <stdio.h>
int main() {
FILE *file = NULL;
error:
// Error handling section
printf("Exiting\n");
return 1;
}
Output
Error opening file
Exiting
Explanation: The program attempts to open a file called [Link],
and if it fails (i.e., fopen() returns NULL), it jumps to the error label
using goto, prints an error message, and returns 1 to indicate failure. If
successful, it prints a success message, closes the file, and returns 0.
Why Use goto for Exception Handling?
The goto statement in C provides a way to jump to a labeled part of the
code. While generally discouraged in modern programming due to
readability and maintainability concerns, goto can be a clean solution for
error handling in specific scenarios like:
Cleaning up allocated resources.
Breaking out of nested loops or blocks.
Handling errors in a single exit path.
Examples of goto for Exception Handling
The following examples demonstrate the use of goto for exception
handling in C:
The try catch statements are used for exception handling in C++ and
Java.
#include <stdio.h>
int main() {
FILE *file = NULL;
int result = 0;
error:
Output
Error opening file
#include <stdio.h>
#include <stdlib.h>
cleanup:
// Cleanup resources
if (buffer) free(buffer);
if (file) fclose(file);
int main() {
const char *filename = "[Link]";
if (processFile(filename) != 0) {
fprintf(stderr, "An error occurred while"
"processing the file.");
return 1;
}
return 0;
}
Output
Error: Failed to open file '[Link]'
An error occurred whileprocessing the file.
File operations are a common task in C programming, but they can
encounter various errors that need to be handled gracefully. Proper error
handling ensures that your program can handle unexpected situations,
such as missing files or insufficient permissions, without crashing. In this
article, we will learn how to handle some common errors during file
operations in C.
Here are some common errors that can occur during file operations:
Error Cause
Permission
Insufficient permissions to access the file.
Denied
Exists
Invalid File
Using a null or invalid file pointer for file operations.
Pointer
Failure to check for errors then the program may behave abnormally
therefore an unchecked error may result in premature termination for the
program or incorrect output.
int main() {
Output
Error: No such file or directory
In the above program, fopen() returns a NULL pointer because the file is
not present in the current directory, then the perror() function prints the
error message.
When writing to a file, ensure the disk has enough space. Errors during
file operations can be detected using ferror(). In the below program, we
assume that there is no space in memory to store any data.
#include <stdio.h>
int main() {
FILE *fptr = fopen("[Link]", "w");
if (fptr == NULL) {
perror("Error opening file");
return 1;
}
Output
Error writing to file: Permission Denied
When creating a new file with fopen() in w mode, the existing file will be
overwritten. To avoid this, we open a new file in wx mode because if file is
already present then fopen() return NULL and set the EEXIST value to
the errno. In the below program, we assume that "[Link]" file is already
present in current directory.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
int main() {
FILE *fptr;
if (fptr == NULL) {
Output
File already exist
Always verify that the file pointer is not NULL before performing
operations like reading or writing.
When we are reading data from a file and the file pointer reaches the end
of the file, we can use the feof() function to handle the end of the file.
#include <stdio.h>
int main() {
FILE *file = fopen("[Link]", "r");
Output
End of file reached.
Whenever we attempt to open a file and the file cannot be opened due to
some error, the fopen() function returns NULL. We can handle this easily
using an if-else statement.
#include <stdio.h>
int main() {
FILE *file = fopen("[Link]", "r");
if (file == NULL) {
printf("File could not be opened.\n");
} else {
printf("File opened successfully.\n");
fclose(file);
}
return 0;
}
Output
File could not be opened.
File Closing Error
Sometimes, when we are closing a file using the fclose() function and it
fails to close the file due to an error, it returns -1.
#include <stdio.h>
int main() {
FILE *fptr = fopen("[Link]", "w");
In C programming, there is no built-in exception handling like in other
high-level languages such as C++, Java, or Python. However, you can still
handle exceptions using error checking, function return values, or by
using signal handlers.
There are 2 methods to handle divide-by-zero exception mentioned below:
Manually Checking before Division
Signal handling
Manually Checking before Division
The most used method is to check if the divisor (number that divides the
dividend) in the division is zero or not using if else statement.
Example:
#include <stdio.h>
#include <float.h>
int main() {
float a = 10, b = 0;
float res;
Output
Error: Division by zero
Explanation: The program checks for division by zero using if (b ==
0) before performing the division. If b is 0, it prints an error message.
Using Signal Handling
Signal handling can be used to catch runtime exceptions like divide-by-
zero errors. In case of floating-point errors, SIGFPE (Floating Point
Exception) is raised. We can create a signal handler function and assign it
to SIGFPE using signal() function. The setjump and longjmp can be
used to jump to the previous valid state of the program, allowing you to
handle the exception and resume execution. The SIGFPE can be cleared
by using the feclearexcept and fetestexcept functions from the fenv.h
header
Example:
#include <fenv.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <float.h>
jmp_buf recovery;
Output
inf
Explanation: Program executes step by step as follow:
The program sets up a signal handler for SIGFPE to catch floating-
point exceptions like division by zero.
It attempts to divide a by b, where b is 0, which will cause a floating-
point exception.
If the division by zero exception occurs and raise(SIGFPE) will trigger
the signal handler (handle_divide_by_zero()).
The signal handler prints an error message and then uses longjmp() to
return to the recovery point, avoiding the program crashing.
The program can then proceed without further errors (or cleanup
operations), as it has recovered from the division by zero.
Miscellaneous Concepts
This section explores various essential of C language that do not fit into a
single category but play a vital role in C programming and provide
advanced functionality to your program.
C Preprocessors
Last Updated : 20 Aug, 2025
Preprocessors are programs that process the source code before the
actual compilation begins. They are not part of the compilation process
but operate separately, allowing programmers to modify the code before
compilation.
It is the first step that the C source code goes through when being
converted into an executable file.
Main types of Preprocessor Directives are Macros, File Inclusion,
Conditional Compilation and Other directives like #undef, #pragma, etc.
Mainly these directives are used to replace a given section of C code
with another C code. For example, if we write "#define PI 3.14", then PI
is replaced with 3.14 by the preprocessor.
Types of C Preprocessors
All the above preprocessors can be classified into 4 types:
Macros
Macros are used to define constants or create functions that are
substituted by the preprocessor before the code is compiled. The two
preprocessors #define and #undef are used to create and remove
macros in C.
#define token value
#undef token
where after preprocessing, the token will be expanded to its value in the
program.
Example:
#include <stdio.h>
// Macro Definition
#define LIMIT 5
int main(){
for (int i = 0; i < LIMIT; i++) {
printf("%d \n", i);
}
return 0;
}
Output
0
1
2
3
4
In the above program, before the compilation begins, the word LIMIT is
replaced with 5. The word 'LIMIT' in the macro definition is called a
macro template and '5' is macro expansion.
Note There is no semi-colon (;) at the end of the macro definition. Macro
definitions do not need a semi-colon to end.
There are also some Predefined Macros in C which are useful in providing
various functionalities to our program.
A macro defined previously can be undefined using #undef preprocessor.
For example, in the above code,
#include <stdio.h>
// Macro Definition
#define LIMIT 5
// Undefine macro
#undef LIMIT
int main(){
for (int i = 0; i < LIMIT; i++) {
printf("%d \n", i);
}
return 0;
}
Output:
./Solution.c: In function 'main':
./Solution.c:13:28: error: 'MAX' undeclared (first use in
this function)
printf("MAX is: %d\n", MAX);
^
./Solution.c:13:28: note: each undeclared identifier is
reported only once for each function it appears in
#include <stdio.h>
int main(){
int a = 10, b = 5;
Output
Area of rectangle is: 50
Explanation: In the above program, the macro AREA(l, b) is defined to
calculate the area of a rectangle by multiplying its length (l) and breadth
(b). When AREA(a, b) is called, it expands to (a * b), and the result is
computed and printed.
Please refer Types of Macros in C for more examples and types.
File Inclusion
File inclusion allows you to include external files (header files, libraries,
etc.) into the current program. This is typically done using
the #include directive, which can include both system and user-defined
files.
Syntax
There are two ways to include header files.
#include <file_name>
#include "filename"
The '<' and '>' brackets tell the compiler to look for the file in
the standard directory while double quotes ( " " ) tell the compiler to
search for the header file in the source file's directory.
Example:
int main() {
printf("Hello World");
return 0;
}
Output
Hello World
Conditional Compilation
Conditional compilation allows you to include or exclude parts of the code
depending on certain conditions. This is useful for creating platform-
specific code or for debugging. There are the following conditional
preprocessor directives: #if, #ifdef, #ifndef, else, #elif and #endif
Syntax
The general syntax of conditional preprocessors is:
#if
// some code
#elif
// some more code
#else
// Some more code
#endif
#endif directive is used to close off the #if, #ifdef, and #ifndef opening
directives.
Example
#include <stdio.h>
int main(){
return 0;
}
Output
PI is defined
Square is not defined
Explanation: This code uses conditional preprocessor directives (#ifdef,
#elif, and #ifndef) to check whether certain macros (PI and SQUARE)
are defined. Since PI is defined, the program prints "PI is defined", then
checks if SQUARE is not defined and prints "Square is not defined".
Other Directives
Apart from the primary preprocessor directives, C also provides other
directives to manage compiler behaviour and debugging.
#pragma:
#include <stdio.h>
void func1();
void func2();
return 0;
}
Output
Inside main()
The above code will produce the output as given above when run on GCC
compilers while the expected output was:
Expected Output
Inside func1()
Inside main()
Inside func2()
This happens because GCC does not support #pragma startup or exit.
However, you can use the below code for the expected output on GCC
compilers.
#include <stdio.h>
void func1();
void func2();
void func1()
{
printf("Inside func1()\n");
}
void func2()
{
printf("Inside func2()\n");
}
int main()
{
printf("Inside main()\n");
return 0;
}
Output
Inside func1()
Inside main()
Inside func2()
In the above program, we have used some specific syntaxes so that one
of the functions executes before the main function and the other executes
after the main function.
Macros and its types in C
Last Updated : 15 Jul, 2025
In C programming, a macro is a symbolic name or constant that
represents a value, expression, or code snippet. They are defined using
the #define directive, and when encountered, the preprocessor substitutes
it with its defined content.
Example
#include <stdio.h>
// Macro definition
#define LIMIT 5
int main(){
return 0;
}
Output
LIMIT: 5
Explanation: In this code, a macro LIMIT is defined with the
value 5 using the #define directive. The macro LIMIT is then used in the
printf function to print its value, which is 5. The preprocessor replaces the
macro LIMIT with its defined value before the code is compiled, so the
output of the program will display the value 5.
Syntax
The general syntax to define a macro is:
#define MACRO_NAME value
where MACRO_NAME is the name of the macro and value is the code or
value that replaces the macro name. It is to be noted that macros don't
necessarily need to be in uppercase. Instead, it is a convention that
makes it easy to recognize.
Types Of Macros in C
There are two types of macros in C language:
1. Object-Like Macros
Object-like macros are the simplest type of macros. They replace the
macro name with a defined value or expression. These are used for
constants or simple values.
#include <stdio.h>
// Macro definition
#define DATE 31
int main(){
Output
Lockdown will be extended upto 31-MAY-2020
Explanation: In this program, the object-like macro DATE is defined
as 31. It is used in the printf function to insert the value 31 into the
message, which the preprocessor replaces before compilation.
2. Chain Macros
These macros involve chaining multiple macros together. This can be
done by combining different macros in a single macro definition, allowing
for more complex operations. In chain macros first of all parent macro is
expanded then the child macro is expanded.
#include <stdio.h>
// Macro definition
#define INSTAGRAM FOLLOWERS
#define FOLLOWERS 138
int main(){
printf("Geeks for Geeks have %dK"
" followers on Instagram",
INSTAGRAM);
return 0;
}
Output
Geeks for Geeks have 138K followers on Instagram
Explanation: INSTAGRAM is expanded first to produce FOLLOWERS.
Then the expanded macro is expanded to produce the outcome
as 138K. This is called the chaining of macros.
3. Multi-Line Macros
These macros span multiple lines for readability and organization. They
are often used when you need a more complex expression or code block.
To create a multi-line macro you have to use backslash \.
#include <stdio.h>
int main(){
Output
1 2 3
Explanation: In this program, a multi-line macro ELE is defined with the
values 1, 2, 3, which is used to initialize the array arr[]. The preprocessor
replaces ELE with these values.
4. Function-Like Macros
These macros take parameters and behave like functions, allowing you to
define reusable logic for common operations. They are expanded at
compile time. A function-like macro is only lengthened if and only if its
name appears with a pair of parentheses after it. If we don't do this, the
function pointer will get the address of the real function and lead to
a syntax error.
#include <stdio.h>
int main(){
return 0;
}
Output
Minimum: 18
Explanation: In this code, the function-like macro min(a, b) compares two
values and returns the smaller using the ternary operator. The values 18
and 76 are passed to the macro, which returns 18.
Header Files in C
Last Updated : 13 Sep, 2025
In C programming, a header file is a file that ends with the .h extension
and contains features like functions, data types, macros, etc that can be
used by any other C program by including that particular header file using
"#include" preprocessor.
C language uses header files to provide the standard libraries and their
components for use in programs.
Example:
int main() {
Output
Hello
In the above program, we have included the stdio.h header file that
provides the standard input and output library in C.
Include Header Files
We have to include header files in our C program to use its features.
There are two ways to do that:
// for header files in system/default
// directory
#include <filename.h>
Standard header files contain the libraries defined in the ISO standard of
the C programming language. They are stored in the system directory of
the compiler and are present in all the C compilers from any vendor.
There are 31 standard header files in the latest version of C language.
Following is the list of some commonly used header files in C:
<signal.h> It is used to perform signal handling functions like signal() and raise().
<limits.h> It determines the various properties of the various variable types. The
Header File Description
Example
The below example demonstrates the use of some commonly used
header files in C.
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
char s1[20] = "12345";
char s2[10] = "Geeks";
char s3[10] = "ForGeeks";
long int res;
Output
Using math.h, The value is: 729
Using stdlib.h, the string to long int: 12345
Using string.h, the strings s2 and s3: ForGeeks ForGeeks
Non-standard header files are not part of the language's ISO standard.
They are generally all the header files defined by the programmers for
purposes like containing custom library functions etc or provided as
external libraries by different vendors. They are manually installed by the
user or maybe part of the compiler by some specific vendor.
There are lots of non-standard libraries for C language. Some commonly
used non-standard/user-defined header files are listed below:
return 0;
}
Output
Press any key to print message
Hello! Geek
The time.h header file contains definitions of functions to get and
manipulate date and time information. It also includes functions, types,
and macros, which are used by programs to measure time, manipulate
dates, and format time information. It describes three time-related data
types.
1. clock_t: clock_t represents the processor time and is used to measure
the CPU clock cycles.
2. time_t: time_t represents the clock time as an integer which shows the
number of seconds since the beginning of the Unix Period( 1st
January, 1970), which is a part of the calendar time.
3. struct tm: struct tm holds the components of date and time such as
hours, minutes, seconds, day, month, year. The struct tm contains:
struct tm {
// seconds, range 0 to 59
int tm_sec;
// minutes, range 0 to 59
int tm_min;
// hours, range 0 to 23
int tm_hour;
// month, range 0 to 11
int tm_mon;
The time.h header file contains time realted operations like getting the
current time, converting between different time formats. It also
contains CLOCKS_PER_SEC macro which holds the number of times
does the system clock ticks per second.
Functions in Time Library
Function
Name Explanation
difftime() This function returns the difference between the times provided.
This function prints the UTC (Coordinated Universal Time) Time and
gmtime() date.
Format for both gmtime() and asctime() is same
mktime() This function returns the calendar-time equivalent using struct tm.
#include <stdio.h>
#include <time.h>
int main() {
Output
Tue Apr 15 07:22:42 2025
int main() {
Output
Tue Apr 15 12:44:17 2025
#include <stdio.h>
#include <time.h>
int main() {
time_t start, end;
// Record endtime
end = time(NULL);
Output
Sum of 0 and 0 is 0
Time taken to print sum is 0.00 seconds
Note: If user gives input slowly that time also add up for total execution
time.
Time using CPU Clock
The program uses clock() function defined in time.h which is used to find
the number of clock ticks during the executions of a code. Which allows
us to measure the time taken by CPU to execute the code.
#include <math.h>
#include <stdio.h>
#include <time.h>
int frequency_of_primes(int n)
{
// This function checks the number of
// primes less than the given parameter
int i, j;
int freq = n - 1;
for (i = 2; i <= n; ++i)
for (j = sqrt(i); j > 1; --j)
if (i % j == 0) {
--freq;
break;
}
return freq;
}
int main()
{
clock_t t;
int f;
t = clock();
f = frequency_of_primes(9999);
printf("The number of primes lower"
" than 10, 000 is: %d\n",
f);
t = clock() - t;
printf("No. of clicks %ld clicks (%f seconds).\n",
t, ((float)t) / CLOCKS_PER_SEC);
return 0;
}
Output
The number of primes lower than 10, 000 is: 1229
No. of clicks 2837 clicks (0.002837 seconds).
#include <stdio.h>
#include <time.h>
int main()
{
time_t rawtime;
struct tm* timeinfo;
time(&rawtime);
timeinfo = localtime(&rawtime);
strftime(buffer, 80,
"Time is %I:%M %p.",
timeinfo);
return 0;
}
Output
Time is 09:00AM.
In C, linkage is a concept that describes how names/identifiers can or
cannot refer to the same entity throughout the whole program or a single
translation unit. The above sounds similar to scope, but it is not so. To
understand what the above means, let us dig deeper into the compilation
process.
Before moving to learn about linkage in C, we first need to
understand what a translation unit is.
A translation unit is a file containing source code, header files and other
dependencies. All of these sources are grouped together to form a single
translation unit which can then be used by the compiler to produce one
single executable object.
What is a Linkage?
Assume a C program that consists of multiple source code files. Each
source file is compiled one at a time. In the compilation process, the last
stage is linking where s multiple machine code files are used to produce
an executable object code. It is handled by the program called linker.
Linkage is a property that describes how variables should be linked
by the linker.
Should a variable be available for another file to use? Should a variable
be used only in the file declared? Both are decided by linkage. Linkage
thus allows you to couple names together on a per file basis.
Types of Linkage in C
There are 2 types of linkage in C:
Internal Linkage
An identifier implementing internal linkage is not accessible outside the
translation unit it is declared in. Any identifier within the unit can access
an identifier having internal linkage. It is implemented by the
keyword static. An internally linked identifier is stored in initialized or
uninitialized segment of RAM. For example,
Consider a source file: animals.c
#include <stdio.h>
#include <stdio.h>
#include "animals.c"
int main() {
// Accessing variable.
printf("%d", animals);
return 0;
}
On compiling and executing feed.c using the following command:
gcc feed.c -o feed
./feed
We get the output,
8
Now, consider that feed.c is located in a different translation unit (means
we are not including the animals.c using #include). Trying to compile it
using the following command:
gcc feed.c animals.c -o feed
./feed
Compiler will throw an error
feed.c: In function 'main':
feed.c:6:18: error: 'animals' undeclared (first use in this
function)
6 | printf("%d", animals);
| ^~~~~~~
feed.c:6:18: note: each undeclared identifier is reported
only once for each function it appears in
External Linkage
An identifier implementing external linkage is visible to every translation
unit. Externally linked identifiers are shared between translation units and
are considered to be located at the outermost level of the program. It is
the default linkage for globally scoped variables and functions.
The keyword extern implements external linkage. When we use the
keyword extern, we tell the linker to look for the definition elsewhere.
Thus, the declaration of an externally linked identifier does not take up
any space. Extern identifiers are generally stored in initialized/uninitialized
or text segment of RAM.
Take the above example of internal linkage and remove the static
keyword.
animals.c
#include <stdio.h>
#include <stdio.h>
int main() {
// Accessing variable.
printf("%d", animals);
return 0;
}
Now, compiling and executing both files using the command:
gcc feed.c animals.c -o feed
./feed
We get the output:
8
As we can see, as the variable animals have external linkage, we were
able to access it in the other translation unit.
Example of External and Internal Linkage
Let's take a look at an interesting example of external and internal linkage.
Modify the file animals.c as shown:
#include <stdio.h>
#include <stdio.h>
int main() {
printAnimals()
return 0;
}
Guess what will happen?
If you guessed that the value of animals variable will be printed, then you
are correct. It is because we are not accessing the value of animals in
other translation unit. We are just accessing the function printAnimals()
which is externally linked and in turn belongs to the same translation unit
as animals. So it is able to access the value of the variable animals.
Storage Classes in C
Last Updated : 24 Jan, 2025
In C, storage classes define the lifetime, scope, and visibility of variables.
They specify where a variable is stored, how long its value is retained,
and how it can be accessed which help us to trace the existence of a
particular variable during the runtime of a program.
In C, there are four primary storage classes:
Table of Content
auto
register
static
extern
auto
This is the default storage class for all the variables declared inside a
function or a block. Auto variables can be only accessed within the
block/function they have been declared and not outside them (which
defines their scope).
Properties of auto Variables
Scope: Local
Default Value: Garbage Value
Memory Location: RAM
Lifetime: Till the end of its scope
Example:
#include <stdio.h>
int main() {
Output
10
Explanation: The auto keyword is used to declare a local variable with
automatic storage. However, in C, local variables are automatically auto
by default, so specifying auto is optional.
auto keyword is not used in front of functions as functions are not limited
to block scope.
static
This storage class is used to declare static variables that have the
property of preserving their value even after they are out of their scope!
Hence, static variables preserve the value of their last use in their scope.
Properties of static Storage Class
Scope: Local
Default Value: Zero
Memory Location: RAM
Lifetime: Till the end of the program
Example:
#include <stdio.h>
void counter() {
// Prints: Count = 1
counter();
// Prints: Count = 2
counter();
return 0;
}
Output
Count = 1
Count = 2
Explanation: The static variable count retains its value between function
calls. Unlike local variables, which are reinitialized each time the function
is called, static variables remember their previous value.
register
This storage class declares register variables that have the same
functionality as that of the auto variables. The only difference is that the
compiler tries to store these variables in the register of the microprocessor
if a free register is available making it much faster than any of the other
variables.
Properties of register Storage Class Objects
Scope: Local
Default Value: Garbage Value
Memory Location: Register in CPU or RAM
Lifetime: Till the end of its scope
Note: The compiler may ignore the suggestion based on available
registers.
#include <stdio.h>
int main() {
Output
0 1 2 3 4
Explanation: The register keyword suggests storing the variable in a
register for faster access, but the compiler may not always honor this
request based on available CPU registers.
extern
Extern storage class simply tells us that the variable is defined elsewhere
and not within the same block where it is used. Basically, the value is
assigned to it in a different block and this can be overwritten/changed in a
different block as well.
Also, a normal global variable can be made extern as well by placing the
‘extern’ keyword before its declaration/definition in any function/block.
Properties of extern Storage Class Objects
Scope: Global
Default Value: Zero
Memory Location: RAM
Lifetime: Till the end of the program.
For example, the below file contains a global variable.
printVar.c
#include <stdio.h>
// Global variable
int globalVar = 100;
void printGlobalVar();
int main() {