In C programming, efficient and dependable code can only be achieved successfully by understanding the complexities that involve the memory model and compiler optimizations. The volatile keyword undoubtedly is crucial in the functioning of the program in several situations especially when dealing with real-time programming, hardware interfaces, and multithreads. As C compilers are designed to optimise code to improve performance, some variables particularly those associated with hardware or interrupt service procedures might be altered beyond the control of the application.
In this guide, we will cover everything in detail about the Volatile keyword in C. We will learn how the volatile keyword in C works, its applications, advantages and disadvantages, and various code examples.
What is a Volatile Keyword in C?
Many programming languages allocate space in memory for storing variables so the compiler tries to make the best use of the available resources when accessing the data and performing operations. However, when working in programming environments where external processes such as hardware, interrupts, and threads can manipulate the variables, such optimization processes can lead to bizarre consequences. In this context, the volatile keyword is very important. Volatile keyword tells the compiler that this variable should not be regarded to be within the focus of the program being run as it can change at any point.
The volatile keyword tells the compiler that the value of a variable may be altered by an external event, such as:
Hardware updates (registers or ports)
Interrupt service routines (ISRs)
Concurrent access in multithreaded programs
In simpler words, a volatile keyword instructs the compiler to get its value every time it fetches the variable, which in turn, stops the compiler from deleting redundant reads or writes.
The basic syntax for declaring the volatile keyword with data types in C is as follows:
volatile data_type variable_name
In the above syntax, a volatile variable of the designated data type is declared and given the variable name. To indicate that the variable’s value could change unexpectedly and that the compiler shouldn’t optimise it, therefore the keyword `volatile` is positioned before the variable name.
volatile with Pointers
The basic syntax for declaring the volatile keyword with pointers in C is as follows:
1. Pointer to volatile Data
volatile data_type *ptr_name;
Here, ptr_name is a pointer to volatile data_type, meaning the data type value it points to can change unexpectedly. This requires any dereferencing of *ptr_name to read directly from memory.
2. volatile Pointer to Data
data_type *volatile ptr_name;
Here, ptr_name itself is volatile, meaning the memory address stored in ptr_name may change unexpectedly. However, the data pointed to by ptr_name is non-volatile.
3. volatile Pointer to volatile Data
volatile data_type *volatile ptr_name;
Here, both ptr_name (the pointer) and the data it points to are volatile. This is helpful in cases when the data it references and the pointer itself may be altered externally.
These were the two ways by which we can use the volatile keyword in C so that our C program code can operate predictably and consistently in dynamic situations.
Get curriculum highlights, career paths, industry insights and accelerate your technology journey.
Download brochure
Why Use Volatile?
There are several reasons to use volatile keywords in C, as it ensures each access reads directly from memory, and the compiler doesn’t optimise or reorder accesses to the volatile variables, which is crucial when the variable can change without the compiler’s knowledge.
The volatile keyword is essential in the following scenarios:
Hardware Programming: When working with hardware registers that fluctuate without the program’s intervention.
Interrupt Service Routines (ISR): When an ISR and main code share a variable.
Multi-threaded Programming: Programming that uses variables shared by several threads.
How does the Volatile Keyword work in C?
The term “volatile” in C indicates to the compiler that this variable might change the value at any time, for instance, if the hardware changes or the code jumps to an INTERUPT SERVICE ROUTINE – all these aspects are beyond the purview of the C compiler. In more detail, a programmer requests the compiler to:
Avoid Optimising Access: Most compilers do optimizations where they keep the values of variables in CPU registers or change the order of lines of code for better performance. So with volatile, the compiler cannot be sure that a particular value of a variable will be the one used in between two calls of that variable.
This means that every time the variable is active, it has to be accessed directly from memory and not from its cached copy in a register.
Prevent Code Reordering for Volatile Variables: To enhance performance, it’s common for compilers to rearrange the order of lines, however, this can present problems when code invoking volatile variables interacts with these reordered lines, especially when these volatile variables are modified elsewhere within code or even external programs.
Apply Strict Rules for Accessing Memory: For all available variable qualifiers, ‘volatile’ is the most crucial since it indicates that the value in these variables should not be cached in any way as they constantly change so there always exists some kind of a pointer to these variables in the memory.
Example:
Consider a scenario with a hardware status register that updates independently of the main program:
volatile int register_st = 0; // declaring a volatile keyword
void wait_for_ready() {
while ((register_st & 0x01) == 0) {
// Loop until the hardware sets bit 0 to 1
}
}
In this example, in the absence of a volatile keyword, the compiler may optimize the code by reading register_st only once and presume that register_st does not change inside the loop. This would stop the loop from identifying hardware modifications.
But by declaring register_st as volatile, the compiler is compelled to verify its value at every iteration, guaranteeing that variations brought about by outside influences are acknowledged.
Volatile keyword is known as the programmer’s friend in C programming. Although it’s a simple keyword, it works well to maintain our code in line with the programming’s unpredictable nature.
Examples of the Volatile Keyword in C
Let’s now see some code examples of using the volatile keyword in C.
Example 1: Using the volatile for memory-mapped counter device in C.
#include <stdio.h>
#define CTR_REGISTER_ADD 0x35000000 // Hypothetical memory address for the counter
// Define a pointer to a volatile int at the memory-mapped address
volatile int* ctr_register = (int*) CTR_REGISTER_ADD;
int main() {
int prev = 0;
printf("Reading from memory-mapped counter device...n");
// Continuously monitor the counter
while (1) {
int curr = *ctr_register; // Read the current value from the counter register
if (curr != prev) {
printf("Counter updated: %dn", curr);
prev = curr;
}
// more code
}
return 0;
}
Output:
Reading from a memory-mapped counter device...
Explanation:
In this example,
We have defined a CTR_REGISTER_ADD as 0x35000000, simulating a hardware counter device.
We declared a ctr_register as a volatile pointer int* pointing to the counter’s address to ensure direct memory access.
Inside an infinite loop, the program continuously reads from *ctr_register and detects changes.
Finally, when curr differs from prev, it prints the updated counter value and sets prev to curr.
Example 2: Using the volatile keyword with ISR in C.
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
volatile int is_itr = 0; // Flag modified by ISR
void handle_signal(int sign) {
// ISR sets the flag to indicate an interrupt was received
is_itr = 1;
printf("Interrupt signal received!n");
}
int main() {
// Register signal handler for SIGINT (CTRL + C)
signal(SIGINT, handle_signal);
printf("Press CTRL + C to trigger an interrupt...n");
// Main loop waiting for the interrupt to set is_itr
while (!is_itr) {
//Busy waiting for the signal handler to set the flag
}
printf("Interrupt flag detected in the main program!n");
return 0;
}
Output:
Press CTRL + C to trigger an interrupt...
^CInterrupt signal received!
Interrupt flag detected in the main program!
Explanation:
In this example,
We have defined a volatile flag is_itr as volatile int, modified by the ISR on receiving a signal.
Then a signal handler is created which registers handle_signal for SIGINT, which sets is_itr to 1 upon receiving the signal and prints a message.
The main program waits for is_itr to be set by looping through it repeatedly.
The program then prints “Interrupt flag detected” and ends the loop when is_itr equals 1.
Real-World Use Cases of volatile
1. Using volatile with Hardware Registers
Hardware registers or ports often change their values independently of the software flow. For example, check how the volatile keyword is used to access a hardware status register in C.
Example:
#define ST_REGISTER (*(volatile int *)0x4000)
void checkStatus() {
while ((ST_REGISTER & 0x01) == 0) {
// Wait until bit 0 is set
}
}
In this example, the ST_REGISTER is a memory-mapped register that is subject to change independently of the program. By declaring it as volatile, the software is guaranteed to always check the register’s current value.
2. volatile in Interrupt Service Routines
In embedded systems, variables modified in an ISR should be marked as volatile. See the below example for more details about interrupt service routines in C.
Example:
volatile int flag = 0;
void ISR_handler() {
flag = 1; // Set flag when interrupt occurs
}
void mainLoop() {
while (flag == 0) {
// Wait for ISR to change flag
}
// Proceed after the flag is set
}
In this example, the compiler may optimise away the while loop’s read of the flag, if the flag is not declared volatile, presuming that it cannot change in the program’s regular flow.
3. Multithreading
In multithreaded applications, shared variables often need to be volatile. Here’s how it can be implemented in the C programming language.
Example:
volatile int mySharedData = 0;
void *thread1() {
mySharedData = 1;
return NULL;
}
void *thread2() {
while (mySharedData == 0) {
// Busy-wait until mySharedData changes
}
// Proceed when mySharedData is updated
}
In the absence of a volatile keyword, mySharedData might be stored in a register, preventing thread2 from ever seeing the update that thread1 made.
Advantages of the Volatile Keyword in C
Volatile keyword in C provides various advantages in specific scenarios, enhancing the code readability, and predictability in situations like hardware or system programming. Here are some of the benefits of the volatile keyword in C:
Prevents Dangerous Optimizations
The keyword volatile also disallows the compiler from removing code that may seem redundant at a local level but is required for the entire system. This behaviour is critical when variables are changed outside the regular program flow, such as by an ISR or hardware device.
Ensures Real-Time Data Access
The volatile keyword gives an assurance that each access to the variable will obtain the most recent version stored in the memory. This is valuable in situations involving embedded systems where hardware inputs cause the state of certain variables to change in real-time or frequently.
Critical in Embedded Systems and Hardware Programming
For embedded systems, registers, ports, and memory-mapped I/Os are interfaces that are visible to hardware that operates outside the program flow. Declaring such registers as volatile enables a program to interact with these registers to always read the most up-to-date value of the register.
Code Readability for Concurrency
When volatile is used in a multithreading context, it means that a particular value of a variable may change unexpectedly. This enhances code readability and also informs the coder that some variables are used by other threads or processes and thus cautions against making certain errors in multithreaded programming.
Disadvantages of the Volatile Keyword in C
While the volatile keyword provides benefits in C programming, it also comes with some drawbacks. Here are some of the disadvantages of the volatile keyword in C:
No Thread Safety: As a safety measure, the volatile keyword is not to be used as a substitute for synchronisation primitives. In a multithreaded environment, the volatile variable may return up-to-date value with every access but does not provide any form of mutual exclusion to prevent data races. Therefore, volatility is not enough when attempting to guarantee thread safety.
Increased Memory Access Overhead: These performance penalties might result because the compiler is algorithmically constrained and every time the volatile variable is invoked to be affected, it could go straight to the memory. Hence, on systems where register access is quicker than memory access, such overhead may lower efficiency, particularly in the cusp of loops.
Potential for Misuse: Developers may incorrectly assume that volatile can handle concurrency and atomicity, leading to bugs in multithreaded applications.
Limits Compiler Optimizations: When the variables are declared as volatile, it creates fewer opportunities for the compiler to conduct optimization, hence the performance of such types of applications is lower as opposed to where such markers will not be present.
Less Debugging: Debugging could get more challenging. It could be due to variables changing as the program executes. “Volatile” variables can change abruptly, making it challenging to identify the cause of a problem.
The volatile keyword in C has great uses by systems programmers as well as by hardware-related codes and limited multitasking applications. We have covered the basic definitions of the volatile keyword, its syntax, how it works in C, and various code examples. The various benefits and drawbacks of using the volatile keyword in C have also been discussed in this guide.
The volatile keyword in C has the essence of driving memory accesses as it makes a program responsive to changes made to the data, which is critical in embedded and concurrent settings. However, this volatile keyword has some drawbacks, like it does not address atomicity or thread safety issues, and may inhibit compiler optimizations.
It is important, therefore, to pay close attention to volatile keywords and use them only when necessary and for variables whose circumstances are beyond the current working stack. If you’re someone who wants to kickstart your coding career, consider pursuing Hero Vired’s Certificate Program in DevOps & Cloud Engineering, which is offered in collaboration with Microsoft.
FAQs
What is the volatile keyword in C with an example?
The volatile keyword in C is a type qualifier that tells the compiler not to predict a variable's value because it may be changed by influences that the compiler is ignorant of, like hardware devices or ISRs.
It prevents the compiler from performing an optimization in which the variable is cached within registers and allows the code to always retrieve the variable directly from memory every time it is referenced. For example,
volatile int status_flag;
In the example above, the status_flag is marked as volatile, the compiler is informed that the variable of the said status flag may be changed outside the program flow.
How does the volatile keyword work in C?
The volatile keyword in C is used to inform the compiler not to apply optimizations for a specific variable or portion of memory as it’s required that the value of the variable is always fetched directly from memory.
This is important in situations where the variable in question has the potential to change unexpectedly which may include, communication with hardware registers, shared memory in a multi-threaded environment, or updates performed by the ISR of the software.
When to use the volatile keyword in C?
The use of the volatile keyword in C applies to the variables that are:
Related to hardware registers: Embedded systems where the program regularly queries a status register of hardware for changes.
Altered by the ISR (Interrupt Service Routine): Since an ISR can be invoked anytime and can change the variable that its invocation dictates it would be mandatory to declare such variable as volatile.
Shared between threads: Volatile can be used when developing multithreading applications, in circumstances where having more than one thread or task modifying the variable value is expected.
Is it possible to use static variables with the volatile keyword in C?
Static variables can be used with the volatile keyword. It has no issues with using both of these keywords in C. To prevent compiler optimizations that could compromise the integrity of a static variable, it is advisable to use volatile if the value is likely to alter outside of the typical program flow.
How to use const with the volatile in C?
The const can be used with the volatile in C. A const volatile variable is read-only to the program but may change due to external factors. It’s particularly useful while working with the memory-mapped hardware registers that are to be read only by the program but are writable by the hardware.
Example:
const volatile int hardware_status = 0x4000;
Here, hardware_status cannot be modified by the program, but it can be changed by the hardware.
Why is the volatile keyword called a qualifier?
The volatile keyword is termed as a "qualifier" because it does exactly that, modifies how a variable behaves and thus how the compiler interprets it. More specifically, it qualifies that the variable’s value may change in ways unknown to the compiler, thereby preventing certain optimizations that could lead to incorrect behaviour.
Hero Vired is a leading LearnTech company dedicated to offering cutting-edge programs in collaboration with top-tier global institutions. As part of the esteemed Hero Group, we are committed to revolutionizing the skill development landscape in India. Our programs, delivered by industry experts, are designed to empower professionals and students with the skills they need to thrive in today’s competitive job market.