embedded software boot camp

Combining C’s volatile and const Keywords

Tuesday, January 24th, 2012 by Michael Barr

Does it ever make sense to declare a variable in C or C++ as both volatile (i.e., “ever-changing”) and const (“read-only”)? If so, why? And how should you combine volatile and const properly?

One of the most consistently popular articles on the Netrino website is about C’s volatile keyword. The volatile keyword, like const, is a type qualifier. These keywords can be used by themselves or together in variable declarations.

I’ve written about volatile and const individually before. If you haven’t previously used the volatile keyword, I recommend you read How to Use C’s volatile Keyword before going on. As that article makes plain:

C’s volatile keyword is a qualifier that is applied to a variable when it is declared. It tells the compiler that the value of the variable may change at any time–without any action being taken by the code the compiler finds nearby.

How to Use C’s volatile Keyword

By declaring a variable volatile you are effectively asking the compiler to be as inefficient as possible when it comes to reading or writing that variable. Specifically, the compiler should generate object code to perform each and every read from a volatile variable and each and every write to a volatile variable–even if you write it twice in a row or read it and ignore the result. No read or write can be skipped. Effectively no optimizations are allowed with respect to volatile variables.

The use of volatile variables also creates additional sequence points in C and C++ programs. The order of accesses of volatile variables A and B in the object code must be the same as the order of those accesses in the source code. The compiler is not allowed to reorder volatile variable accesses for any reason.

Here are a couple of examples of declarations of volatile variables:

int volatile g_flag_shared_with_isr;

uint8_t volatile * p_led_reg = (uint8_t *) 0x00080000;

The first example declares a global flag that can be shared between an ISR and some other part of the code (e.g., a background processing loop in main() or an RTOS task) without fear that the compiler will optimize (i.e., “delete”) the code you write to check for asynchronous changes to the flag’s value. It is important to use volatile to declare all variables that are shared by asynchronous software entities, which is important in any kind of multithreaded programming. (Remember, though, that access to global variables shared by tasks or with an ISR must always also be controlled via a mutex or interrupt disable, respectively.)

The second example declares a pointer to a hardware register at a known physical memory address (80000h)–in this case to manipulate the state of one or more LEDs. Because the pointer to the hardware register is declared volatile, the compiler must always perform each individual write. Even if you write C code to turn an LED on followed immediately by code to turn the same LED off, you can trust that the hardware really will receive both instructions. Because of the sequence point restrictions, you are also guaranteed that the LED will be off after both lines of the C code have been executed. The volatile keyword should always be used with creating pointers to memory-mapped I/O such as this.

[See Coding Standard Rule #4: Use volatile Whenever Possible for more on the use of volatile by itself.]

How to Use C’s const Keyword

The const keyword is can be used to modify parameters as well as in variable declarations. Here we are only interested in the use of const as a type qualifier, as in:

uint16_t const max_temp_in_c = 1000;

This declaration creates a 16-bit unsigned integer value of 1,000 with a scoped name of max_temp_in_c. In C, this variable will exist in memory at run-time, but will typically be located, by the linker, in a non-volatile memory area such as ROM or flash. Any reference to the const variable will read from that location. (In C++, a const integer may no longer exist as an addressable location in run-time memory.)

Any attempt the code makes to write to a const variable directly (i.e., by its name) will result in a compile-time error. To the extent that the const variable is located in ROM or flash, an indirect write (i.e., via a pointer to its address) will also be thwarted–though at run-time, obviously.

Another use of const is to mark a hardware register as read-only. For example:

uint8_t const * p_latch_reg = 0x10000000;

Declaring the pointer this way, any attempt to write to that physical memory address via the pointer (e.g., *p_latch_reg = 0xFF;) should result in a compile-time error.

[See Coding Standard Rule #2: Use const Whenever Possible for more on the use of const by itself.]

How to Use const and volatile Together

Though the essence of the volatile (“ever-changing”) and const (“read-only”) decorators may seem at first glance opposed, there are some times when it makes sense to use them both to declare one variable. The scenarios I’ve run across have involved pointers to memory-mapped hardware registers and shared memory areas.

(#1) Constant Addresses of Hardware Registers

The following declaration uses both const and volatile in the frequently useful scenario of declaring a constant pointer to a volatile hardware register.

uint8_t volatile * const p_led_reg = (uint8_t *) 0x00080000;

The proper way to read a complex declaration like this is from the name of the variable back to the left, as in:

p_led_reg IS A constant pointer TO A volatile 8-bit unsigned integer.

Reading it that way, we can see that the keyword const modifies only the pointer (i.e., the fixed address 80000h), which does not change at run-time. Whereas the keyword volatile modifies only the type of integer. This is actually quite useful and is a much safer version of the declaration of a p_led_reg that appears at the top of this article. In particular, adding const means that the simple typo of a missed pointer dereference (‘*’) will be caught at compile time. That is, the mistaken code p_led_reg = LED1_ON; won’t overwrite the address with the non-80000h value of LED1_ON. The compiler error leads us to correct this to *p_led_reg = LED1_ON;, which is almost certainly what we meant to write in the first place.

(#2) Read-Only Shared-Memory Buffer

Another use for a combination of const and volatile is where you have two processors communicating via a shared memory area and you are coding the side of this communications that will only be reading from a shared memory buffer. In this case you could declare variables such as:

int const volatile comm_flag;

uint8_t const volatile comm_buffer[BUFFER_SIZE];

Of course, you’d usually want to instruct the linker to place these global variables at the correct addresses in the shared memory area or to declare the above as pointers to specific physical memory addresses. In the case of pointers, the use of const and volatile may become even more complex, as in the next category.

(#3) Read-Only Hardware Register

Sometimes you will run across a read-only hardware register. In addition to enforcing compile-time checking so that the software doesn’t try to overwrite the memory location, you also need to be sure that each and every requested read actually occurs. By declaring your variable IS A (constant) pointer TO A constant and volatile memory location you request all of the appropriate protections, as in:

uint8_t const volatile * const p_latch_reg = (uint8_t *) 0x10000000;

As you can see, the declarations of variables that involve both the volatile and const decorators can quickly become complicated to read. But the technique of combining C’s volatile and const keywords can be useful and even important. This is definitely something you should learn to master to be a master embedded software engineer.

Tags: , , , , ,

7 Responses to “Combining C’s volatile and const Keywords”

  1. Mike Acton says:

    “C’s only other decorator keyword is const.” Nope. See: ‘restrict’

  2. Dan Szabo says:

    I’ve become accustomed to using the extended keywords in IAR’s EW IDE to define hardware registers at specific addresses. All hardware registers are defined as volatile, and read only registers are also defined as const. This was to both provide our engineers (myself included) with registers that look like variables instead of pointers to variables, and also to provide more efficient code, as additional variables wouldn’t be required in RAM to access hardware registers. The tradeoff being that the code won’t easily port to other IDEs. That’s always been fair in our projects, as we rarely move between IDEs when developing for a particular part, and the hardware registers need to get redefined anyway (and most likely the usage of them) if you are switching to a new processor.

    Always good to refresh the old memory on volatile and const usage though.

  3. Tim Wescott says:

    There are various ways to achieve what Dan does with IAR’s extensions to C, with varying degrees of elegance and portability.

    My preferred method is to declare registers as ‘extern’ in the C (or C++) code, then actually define their locations as absolutely-located entities in a linker file or an assembly file. This method is also tool-dependent, but since there’s almost always a way to achieve this goal it’s fairly safe. It can be cumbersome if you try to define registers one-by-one, which is why I generally make up a ‘struct’ type for each peripheral (or peripheral type, if the microprocessor designer was kind enough to make duplicate peripherals identical but for offset), then declare entire structures in the C code and in the linker script or assembly.

    Another method is to make up header files with appropriate defines:

    #define p_led_reg ((uint8_t *) 0×00080000)

    etc. This gets more complicated with C++ (the compiler will complain about old-style casts, or outright throw an error), and even more complicated yet if your code is a mix of C and C++. But it’s a method that will work with just about every tool chain that I’ve encountered.

    Probably the best way to do this of all is to make sure that you choose a form for addressing your registers that is tool-independent outside of the actual register definitions (header files and possibly linker scripts &c.) and make sure that those register definitions are well encapsulated and easily identifiable so that if you do have to do some mad thing like changing tool chains in mid stream, the surgery will be limited to the pertinent header files, in stead of being splattered over your whole code base.

  4. Const is also useful when flash is used as storage, and global const variables are used for easy access data structures.
    But then volatile is a must for such variable if you reprogram this flash in runtime using processor specific registers.

  5. ming says:

    Hi,

    Thanks for the sharing. I have one question about the third example of using const and volatile together. You declare a constant pointer to a constant and volatile register as:

    uint8_t const volatile * const p_latch_reg = (uint8_t *) 0×10000000;

    Is the register declared as constant because the register value would not be modified in this program? But the register may be modified by other programs. Therefore, we need declare the register as volatile (that is, reading the value each time).

    On the other hand, do we need declare the register as volatile if no programs would modify the register (that is, the register is really read-only to all)?

    Thanks.

    • Michael Barr says:

      In that example, the latch register is read-only. The address is a constant. Hardware can change the value at any time, so volatile is necessary to ensure the software will always actually read from the register at that address when the value is tested. But no part of the software should be writing to that register, as that might mess with the hardware or is at least a software bug.

  6. Nico says:

    Apart from Krzysztof Wesolowski’s very good example there’s still another use case which is not covered here:

    The combination “const volatile” is used A LOT (for almost every flash variable) in real-time embedded software that can be calibrated/tuned while the system is running. For example, in automotive software using CCP/XCP protocol over CAN.

    Const is used to make sure the software doesn’t try to change the data (force compiler error) but you need the volatile access because the tester can change the value anytime with a tool. The correct address should be checked everytime the variable is accessed. The initial value is in flash but the flash section with const volatile data is copied to RAM at startup. The tool writes to the RAM address.

    Typical use case:
    When “Chiptuning” an engine, the RAM copy of the flash data is overwritten at runtime. When the optimal data is found, the controller is reflashed with the new default values in flash memory.

Leave a Reply