C Programming – When and Why to Use the const Keyword

cconstpatterns-and-practicesreadability

While getting my code reviewed here the issue of using the const keyword came up. I understand that it is used for implementing read-only behaviour on variables.

I am confused about what are the various situations when it can be useful.

  • Should it be used for the sake of clarity in function prototypes?
  • Should it be used as a security measure during code development?
  • Should it be used in the scope of various functions for declaring run-time constants?
  • Should it be used at all?

These question are just examples of the confusion that I am facing. The general confusion is

  • When should be the const keyword used in C programming?
  • What are the various types of benefits that can be gained by using this keyword in C?
  • Are there any cons of using const keyword?

It has been pointed that this question may be too broad due to all these questions in the detail of my question. I just wanted to clarify that these questions are just to clarify the confusion regarding the main question.

When and for what purposes should the const keyword be used in C for variables?

It can also be rephrased as

The proper use of const keyword in C` with the pros and cons of the same.

Best Answer

When reviewing code, I apply the following rules:

  • Always use const for function parameters passed by reference where the function does not modify (or free) the data pointed to.

    int find(const int *data, size_t size, int value);
    
  • Always use const for constants that might otherwise be defined using a #define or an enum. The compiler can locate the data in read-only memory (ROM) as a result (although the linker is often a better tool for this purpose in embedded systems).

    const double PI = 3.14;
    
  • Never use const in a function prototype for a parameter passed by value. It has no meaning and is hence just 'noise'.

    // don't add const to 'value' or 'size'
    int find(const int *data, size_t size, int value); 
    
  • Where appropriate, use const volatile on locations that cannot be changed by the program but might still change. Hardware registers are the typical use case here, for example a status register that reflects a device state:

    const volatile int32_t *DEVICE_STATUS =  (int32_t*) 0x100;
    

Other uses are optional. For example, the parameters to a function within the function implementation can be marked as const.

// 'value' and 'size can be marked as const here
int find(const int *data, const size_t size, const int value)  
{
     ... etc

or function return values or calculations that are obtained and then never change:

char *repeat_str(const char *str, size_t n) 
{
    const size_t len = strlen(str);
    const size_t buf_size = 1 + (len * n);
    char *buf = malloc(buf_size);
    ...

These uses of const just indicate that you will not change the variable; they don't change how or where the variable is stored. The compiler can of course work out that a variable is not changed, but by adding const you allow it to enforce that. This can help the reader and add some safety (although if your functions are big or complicated enough that this makes a great difference, you arguably have other problems). Edit - eg. a 200-line densely coded function with nested loops and many long or similar variable names, knowing that certain variables never change might ease understaning significantly. Such functions have been badly designed or maintened.


Problems with const. You will probably hear the term "const poisoning". This occurs when adding const to a function parameter causes 'constness' to propagate.

Edit - const poisoning: for example in the function:

int function_a(char * str, int n)
{
    ...
    function_b(str);
    ...
}

if we change str to const, we must then ensure that fuction_b also takes a const. And so on if function_b passes the str on to function_c, etc. As you can imagine this could be painful if it propagates into many separate files/modules. If it propagates into a function that cannot be changed (eg a system library), then a cast becomes necessary. So sprinkling const around in existing code is perhaps asking for trouble. In new code though, it is best to const qualify consistently where appropriate.

The more insidious problem of const is that it was not in the original language. As an add-on it doesn't quite fit. For a start it has two meanings (as in the rules above, meaning "I'm not going to change this" and "this cannot be modified"). But more than that, it can be dangerous. For example, compile and run this code and (depending upon the compiler/options) it may well crash when run:

const char str[] = "hello world\n";
char *s = strchr(str, '\n');
*s = '\0';

strchr returns a char* not a const char*. As its call parameter is const it must cast the call parameter to char*. And in this case that casts away the real read-only storage property. Edit: - this applies generally to vars in read-only memory. By 'ROM', I mean not just physical ROM but any memory that is write-protected, as happens to the code section of programs run on a typical OS.

Many standard library functions behave in the same way, so beware: when you have real constants (ie. stored in ROM) you must be very careful not to lose their constness.

Related Topic