const-ant confusion in C.

Updates:

  • 2017-11-30 – Fixing description of MyStructure example. Thanks, Lost Wiz!
  • 2024-10-30 – Fixed some formatting.

Embedded Life

I currently make my living doing embedded C programming. I am not quite sure how to define what “embedded” programming is other than to say: you probably don’t have everything you expect.

You often program on systems without file systems, without gigabytes of RAM and without an operating system to do most of your work for you. For example, at my previous job one of my main platforms (a TI MSP430 processor) had only 10K of RAM and the program was limited to 40K of flash storage. At my current job, I work on several variations of ARM processors, with one configured to give me only 7K of RAM and 36K of program space.

These systems are much closer to an Arduino UNO, which has 32K of flash and 2K of RAM, than a desktop Windows or Linux machine.

Not all embedded systems are this tiny, of course. There are many embedded systems that run Linux, but once you have a full operating system and a file system, the definition of “embedded” seems to be used to just mean “smaller than Windows”.

But I digress…

Over the past six years, I’ve worked on code that was created and maintained by many different programmers before I worked on it. I have learned some cool tricks and also seen some very un-cool tricks (i.e., just wrong). I expect I will be leaving my own cool/un-cool bits of code for future programmers to find.

With that said, there is one item that keeps turning up repeatedly that many of us C programmers don’t seem to really understand because we keep misusing it. And by “we” I include myself.

“const”

There is a keyword in C called “const” which, according to Wikipedia, “indicates that the data is read only.” For example, suppose you wrote a function that accepts a string (actually, pointer to a bunch of characters) like this:

void PrintErrorMessage( char *message )
{
   fputs( "ERROR: ", stderr );
   fputs( message, stderr );
}

In this example, whatever string passed in will be displayed with “ERROR: ” prepended to it.

int main( int argc, char **argv )
{
   PrintErrorMessage("File Not Found");

   return EXIT_SUCCESS;
}

That would display a message to standard error output:

ERROR: File Not Found

But, there is nothing preventing the function from trying* to modify the string that was passed in.

* Key word is “trying”… If that string were embedded in the binary and it was running out of ROM or Flash, attempts to modify it would be rejected by the “hardware can’t do that” exception ;-)

void PrintErrorMessage( char *message )
{
   fputs( "ERROR: ", stderr );

   // Convert message to uppercase
   for (int i=0; i<strlen(message); i++)
   {
      message[i] = toupper(message[i]);
   }
   fputs( message, stderr );
}

The intent here would be to display the error message in uppercase, such as “ERROR: FILE NOT FOUND”. This would work if the string being passed in was modifiable, such as:

char message[80];

strcpy(message, "File Not Found");
PrintErrorMessage( message );

…but after returning from that call, the string would have been modified by the function and would now be “FILE NOT FOUND” in memory. This is fine, if that is the intent of the function, but if you do not intend the string to be modified, you can take steps to prevent the function from being able to do it.

Only read this…

“const” will tell the compiler to not allow code to be built that modifies the variable. You see it used all the time by standard C library functions that take strings, such as puts(), strcpy(), etc.

int puts ( const char * str );

For puts() and similar functions, the use of const disallows modifying that string within the function. In the earlier example, we could make the passed-in string “read only” like this:

void PrintErrorMessage( const char *message )
{
   int i;
   fputs( "ERROR: ", stderr );

   // Convert message to uppercase
   for (int i=0; i<strlen(message); i++)
   {
      message[i] = toupper(message[i]);
   }

   fputs( message, stderr );
}

With that change made, the compiler now should issue warnings (or errors) on attempts to modify the “message” string inside the function:

error: assignment of read-only location '*(message + (sizetype)((unsigned int)i * 1u))'|

The moment the function tries to modify “message[i]” causes a problem, because “const” has told the compiler whatever is passed in should not be modified.

Because of the usefulness of this extra compile-time error checking, const is a good thing to use.

And many of us do.

Incorrectly.

There is a bit of confusion in how const works. In the above example, we pass in the pointer to some memory that contains a string. We do not want the memory that is being passed in to be modified, so we use “const char *message”. According to the “C Gibberish” website, that means:

declare message as pointer to const char

We might also want to prevent the pointer itself from being modified by using “const char const * message” … and that would be incorrect. That is not the proper syntax for “const”:

declare message as pointer to const const char

The confusion comes from const allowing two ways of doing the same thing. Did you know that:

const char

…is the same as:

char const

In C, the true syntax seems to be using “const” after the thing you are declaring, like “char is a constant” or “* is a constant”. But, at the start of that line, const can be used at the beginning to mean the same thing, and since we see that all the time, many of us seem to think that const describes what comes after it. Which it doesn’t.

To properly declare that the pointer and the data it points to should be read-only, it should be:

char const * const message;

C Gibberish agrees:

declare message as const pointer to const char

We need to learn this double “before or after is the same thing” use, or only use the “after” use and be consistent.

// declare message as const pointer to const char
const char * const message;

is the same as

// declare message as const pointer to const char
char const * const message;

My most recent encounter with this was in code at work that did something like this:

void Initialize(const MyStructure *config);

They must have intended this to mean “I’m passing in a constant MyStructure pointer which cannot be modified” but, in reality, what they were getting was:

declare config as pointer to const struct MyStructure

They were telling the compiler that the structure being pointed to was read-only and should not be modified. But the function’s purpose was to modify elements of that structure:

config->type = 10;
config->foo = 'a';

Because of this misuse of const, many compiler warnings were generated.

||=== Build: Debug in Const (compiler: GNU GCC Compiler) ===|
main.c||In function 'Initialize':|
main.c|16|error: assignment of member 'type' in read-only object|
main.c|17|error: assignment of member 'foo' in read-only object|
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

The fix was to correct the prototype and function to do what was actually intended:

// declare config as const pointer to struct MyStructure
void Initialize(MyStructure * const config);

This now disallows the “config” pointer from being changed, but not the structure it points to. Thus, this would not work:

config++; // Increment config pointer.

…but the intended structure modifications would:

config->type = 10;
config->foo = 'a';

I’m sad to say I’ve been using “const” incorrectly as long as I can remember using “const.”

For a future article, I may dive in to some deep const-ant confusion I recently found myself in, and see if someone out there can tell me if I am finally doing it correctly or not.

Until then…

4 thoughts on “const-ant confusion in C.

  1. William Astle

    In the bit where you describe the intended meaning of the MyStructure bit, you’ve actually described exactly what is written in the wrong prototype. “I’m passing in a structure which is constant and can’t be modified, and here is the pointer to it” is exactly the same as “pointer to const struct MyStructure”. I think you meant to describe the intended meaning as “I’m passing in a structure to be modified and here’s a pointer to it which must not be modified.”

    Reply
  2. Darren

    Although less commonly seen, the same confusion exists for ‘volatile’, but compilers probably won’t warn you when you use that incorrectly.

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.