`const char * const` versus `const char *`?

C++

C++ Problem Overview


I'm running through some example programs to refamiliarize myself with C++ and I have run into the following question. First, here is the example code:

void print_string(const char * the_string)
{
	cout << the_string << endl;
}

int main () {
	print_string("What's up?");
}

In the above code, the parameter to print_string could have instead been const char * const the_string. Which would be more correct for this?

I understand that the difference is that one is a pointer to a constant character, while the other one is a constant pointer to a constant character. But why do both of these work? When would it be relevant?

C++ Solutions


Solution 1 - C++

The latter prevents you from modifying the_string inside print_string. It would actually be appropriate here, but perhaps the verbosity put off the developer.

char* the_string : I can change which char the_string points to, and I can modify the char to which it points.

const char* the_string : I can change which char the_string points to, but I cannot modify the char to which it points.

char* const the_string : I cannot change which char the_string points to, but I can modify the char to which it points.

const char* const the_string : I cannot change which char the_string points to, nor can I modify the char to which it points.

Solution 2 - C++

  1. Mutable pointer to a mutable character

     char *p;
    
  2. Mutable pointer to a constant character

     const char *p;
    
  3. Constant pointer to a mutable character

     char * const p; 
    
  4. Constant pointer to a constant character

     const char * const p;
    

Solution 3 - C++

const char * const means pointer as well as the data the pointer pointed to, are both const!

const char * means only the data the pointer pointed to, is const. pointer itself however is not const.

Example.

const char *p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //okay, changing the non-const pointer. 

const char * const p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //error, changing the const pointer. 

Solution 4 - C++

(I know this is old but I wanted to share anyway.)

Just wanted to elaborate on Thomas Matthews' answer. The Right-Left Rule of C type declarations pretty much says: when reading a C type declaration start at the identifier and go right when you can and left when you can't.

This is best explained with a couple examples:

Example 1

  • Start at the identifier, we can't go right so we go left

      const char* const foo
                  ^^^^^
    

foo is a constant...

  • Continue left

      const char* const foo
                ^
    

foo is a constant pointer to...

  • Continue left

      const char* const foo
            ^^^^
    

foo is a constant pointer to char...

  • Continue left

      const char* const foo
      ^^^^^
    

foo is a constant pointer to char constant (Complete!)

Example 2

  • Start at the identifier, we can't go right so we go left

      char* const foo
            ^^^^^
    

foo is a constant...

  • Continue left

      char* const foo
          ^
    

foo is a constant pointer to...

  • Continue left

      char* const foo
      ^^^^
    

foo is a constant pointer to char (Complete!)

Example 1337

  • Start at the identifier, but now we can go right!

      const char* const* (*foo[8])()
                              ^^^
    

foo is an array of 8...

  • Hit parenthesis so can't go right anymore, go left

      const char* const* (*foo[8])()
                          ^
    

foo is an array of 8 pointer to...

  • Finished inside parenthesis, can now go right

      const char* const* (*foo[8])()
                                  ^^
    

foo is an array of 8 pointer to function that returns...

  • Nothing more to the right, go left

      const char* const* (*foo[8])()
                       ^
    

foo is an array of 8 pointer to function that returns a pointer to a...

  • Continue left

      const char* const* (*foo[8])()
                  ^^^^^
    

foo is an array of 8 pointer to functions that returns a pointer to a constant...

  • Continue left

      const char* const* (*foo[8])()
                ^
    

foo is an array of 8 pointer to functions that returns a pointer to a constant pointer to a...

  • Continue left

      const char* const* (*foo[8])()
            ^^^^
    

foo is an array of 8 pointer to functions that returns a pointer to a constant pointer to a char...

  • Continue left

      const char* const* (*foo[8])()
      ^^^^^
    

foo is an array of 8 pointer to functions that returns a pointer to a constant pointer to a char constant (Complete!)

Further explanation: http://www.unixwiz.net/techtips/reading-cdecl.html

Solution 5 - C++

Many people suggest reading the type specifier from right to left.

const char * // Pointer to a `char` that is constant, it can't be changed.
const char * const // A const pointer to const data.

In both forms, the pointer is pointing to constant or read-only data.

In the second form, the pointer cannot be changed; the pointer will always point to the same place.

Solution 6 - C++

const char * means that you can't use the pointer to change what is pointed to. You can change the pointer to point to something else, though.

Consider:

const char * promptTextWithDefault(const char * text)
{
    if ((text == NULL) || (*text == '\0'))
        text = "C>";
    return text;
}

The parameter is a non-const pointer to const char, so it can be change to another const char * value (like a constant string). If, however, we mistakenly wrote *text = '\0' then we'd get a compilation error.

Arguably, if you don't intend to change what the parameter is pointing to, you could make the parameter const char * const text, but it's not common to do so. We usually allow functions to change the values passed to parameters (because we pass parameters by value, any change does not affect the caller).

BTW: it is good practice to avoid char const * because it's often misread - it means the same as const char *, but too many people read it as meaning char * const.

Solution 7 - C++

Nearly all of the other answers are correct, but they miss one aspect of this: When you use the extra const on a parameter in a function declaration, the compiler will essentially ignore it. For a moment, let's ignore the complexity of your example being a pointer and just use an int.

void foo(const int x);

declares the same function as

void foo(int x);

Only in the definition of the function is the extra const meaningful:

void foo(const int x) {
    // do something with x here, but you cannot change it
}

This definition is compatible with either of the declarations above. The caller doesn't care that x is const--that's an implementation detail that's not relevant at the call site.

If you have a const pointer to const data, the same rules apply:

// these declarations are equivalent
void print_string(const char * const the_string);
void print_string(const char * the_string);

// In this definition, you cannot change the value of the pointer within the
// body of the function.  It's essentially a const local variable.
void print_string(const char * const the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // COMPILER ERROR HERE
}

// In this definition, you can change the value of the pointer (but you 
// still can't change the data it's pointed to).  And even if you change
// the_string, that has no effect outside this function.
void print_string(const char * the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // OK, but not observable outside this func
}

Few C++ programmers bother to make parameters const, even when they could be, regardless of whether those parameters are pointers.

Solution 8 - C++

There's no reason why either one wouldn't work. All print_string() does is print the value. It doesn't try to modify it.

It's a good idea to make function that don't modify mark arguments as const. The advantage is that variables that can't change (or you don't want to change) can be passed to these functions without error.

As far as the exact syntax, you want to indicate which type of arguments are "safe" to be passed to the function.

Solution 9 - C++

The difference is that without the extra const the programmer could change, inside the method, where the pointer points to; for example:

 void print_string(const char * the_string)
 {
    cout << the_string << endl;
    //....
    the_string = another_string();
    //....

 }

That would be instead illegal if the signature were void print_string(const char * const the_string)

Many programmers feel too verbose (in most scenarios) the extra const keyword and omit it, even though it would be semantically correct.

Solution 10 - C++

In the latter you are guaranteeing not to modify both pointer and character in the first you only guarantee that the contents will not change but you may move the pointer around

Solution 11 - C++

I think it's vary rarely relevant, because your function isn't getting called with arguments like &*the_string or **the_string. The pointer itself is a value-type argument, so even if you modify it you're not going to change the copy that was used to call your function. The version you're showing ensures that the string will not change, and I think that's sufficient in this case.

Solution 12 - C++

The difference between the two is that char* can point to any arbitrary pointer. Const char* by contrast, points to constants defined in the DATA section of the executable. And, as such, you cannot modify the character values of a const char* string.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionpictView Question on Stackoverflow
Solution 1 - C++Kent BoogaartView Answer on Stackoverflow
Solution 2 - C++James Michael HareView Answer on Stackoverflow
Solution 3 - C++NawazView Answer on Stackoverflow
Solution 4 - C++GarrettView Answer on Stackoverflow
Solution 5 - C++Thomas MatthewsView Answer on Stackoverflow
Solution 6 - C++TonyRView Answer on Stackoverflow
Solution 7 - C++Adrian McCarthyView Answer on Stackoverflow
Solution 8 - C++Jonathan WoodView Answer on Stackoverflow
Solution 9 - C++leonbloyView Answer on Stackoverflow
Solution 10 - C++Jesus RamosView Answer on Stackoverflow
Solution 11 - C++EglinView Answer on Stackoverflow
Solution 12 - C++MazView Answer on Stackoverflow