Writeable Strings

Introduction

One requirement of the Coding Guidelines for this course is that you include the -Wwrite-strings option when you invoke the g++ compiler. The purpose of this handout is to show you how to accomplish common tasks in your C++ programs without getting warning messages from the compiler.

The Problem

Assume you want to have a char * variable which sometimes points to one message, and other times points to another one. A first attempt might look like this:
      char *msg;
            msg = "hello";
            msg = "good-bye";
(Of course, real code would have these three statements interspersed with other, meaningful, code.)

This code will give you warning messages for the two assignment statements, saying "deprecated conversion from string constant to 'char *'" The explanation is that your code should not be able to modify the contents of string literals, like "hello", but the above code, if allowed to compile, would allow you to write statements like the following:

      char *msg = "hello";
           *msg = 'j';
           printf( "%s\n", "hello" );
This would let you change the literal "hello" into "jello"! In fact, if you compile this code on forbin, you will get a warning message from the compiler, but will get an executable program that actually prints "jello" when you run it. Clearly, this is not a good thing to have happening in your programs. (If you run the same code on a Linux system, you will get a memory fault when the program tries to modify the string constant. The difference has to do with how the two different operating systems protect different regions of memory.)

Correct Solution

The correct solution to the problem is to declare msg to be a pointer to a constant string:
      const char  *msg;
                   msg = "hello";
                   msg = "good-bye";
That code compiles with no warning messages, and probably does just what you wanted to accomplish. But, having done this, you will get an error message (not just a warning) from the compiler if you try something like:
                  *msg = 'j';
... which is also what you want! It would be wrong to try to change a string constant.

Note that the following code also compiles without any warnings and executes without any problems:

      const char  *msg;
            char   buf[ 10 ];
            sprintf( buf, "%03d\n", 7 );
            msg = buf;
It's okay to change the contents of the buf array because it has not been declared to be constant. In this case, msg will point to the string, "007\n". Then statements like
            *buf = 'x';
will compile and execute okay, but statements like
            *msg = 'x';
will generate a warning from the compiler because you have declared that msg points to characters that are not to be modified.

(Mis)using Casts

The main use of casts is to tell the compiler that you know what you are doing, and that you want to be allowed to do something that normally doesn't make sense. For example, this code will compile with no warnings:
        char  *msg;
               msg = (char *) "hello";
That is, this code "casts away" the const-ness of the literal "hello" when assigning a pointer to it to the variable msg.

But if you put in this cast, the compiler will then compile the following statement with no error or warning messages:

              *msg = 'j';
and you won't know about your mistake until run-time, when your program will either generate a memory fault (on Linux) or produce wrong results, like "jello", (on forbin). It's a lot easier to fix problems if the compiler tells you where you made your mistake than trying to figure out what went wrong at run time, but casting away const-ness is a way of getting the compiler to suppress useful warning messages.

The moral is:

Never cast away the const-ness of character strings!

Constant Pointers

There are two possible places you can put the const qualifier, leading to four combinations:
      const char* const msg_0;
      const char       *msg_1;
            char* const msg_2;
            char       *msg_3;
Here, msg_0 is a constant pointer to a constant string. The statement shown will give a compile-time error because msg_0 is not made to point to anything, and there is no way to assign a pointer to it later on because of the const*. For example:
      const char const *msg_0 = "hello";
would compile okay, and there would then be no way either to execute either:
        *msg_0 = 'j';
or
         msg_0 = "good-bye";
Basically, this makes msg_0 synonymous with the string constant, "hello".

We have already looked at code equivalent to msg_1 earlier. This pointer can be assigned pointers to both constant and variable strings, but cannot be used to modify any string that it points to.

There will also be an error if you try to compile the definition of msg_2 for the same reason there would be an error for msg_0. Because the pointer itself is constant, it has to be given a value when the variable is declared. But having done so, it would be syntactically correct to change the string pointed to by this variable:

      char        buf[ 10 ];
      char const *msg_2 = buf;
With this code, msg_2 points to buf[ 0 ], and it must do so forever.

And there's not much to say about msg_3. You can change the pointer, and you can change what the pointer points to.

Reminder

Pointers and arrays are two different things, despite what you might hear elsewhere. In this web page, variables with msg in their names are all pointers, and buf is always an array. But if you refer to an array without a subscript, the compiler will generate a hidden pointer to the first element of the array for you.