62

The following code snippet (correctly) gives a warning in C and an error in C++ (using gcc & g++ respectively, tested with versions 3.4.5 and 4.2.1; MSVC does not seem to care):

char **a;
const char** b = a;

I can understand and accept this.
The C++ solution to this problem is to change b to be a const char * const *, which disallows reassignment of the pointers and prevents you from circumventing const-correctness (C++ FAQ).

char **a;
const char* const* b = a;

However, in pure C, the corrected version (using const char * const *) still gives a warning, and I don't understand why. Is there a way to get around this without using a cast?

To clarify:

  1. Why does this generate a warning in C? It should be entirely const-safe, and the C++ compiler seems to recognize it as such.

  2. What is the correct way to go about accepting this char** as a parameter while saying (and having the compiler enforce) that I will not be modifying the characters it points to? For example, if I wanted to write a function:

     void f(const char* const* in) {
       // Only reads the data from in, does not write to it
     }
    

And I wanted to invoke it on a char**, what would be the correct type for the parameter?

smbear
  • 1,007
  • 9
  • 17
HappyDude
  • 2,566
  • 3
  • 18
  • 14

6 Answers6

61

I had this same problem a few years ago and it irked me to no end.

The rules in C are more simply stated (i.e. they don't list exceptions like converting char** to const char*const*). Consequenlty, it's just not allowed. With the C++ standard, they included more rules to allow cases like this.

In the end, it's just a problem in the C standard. I hope the next standard (or technical report) will address this.

ForceBru
  • 43,482
  • 10
  • 63
  • 98
Kevin
  • 25,207
  • 17
  • 54
  • 57
  • I certainly didn't vote it down; it's the most relevant answer I've gotten so far. If I had 100 reputation points I would vote this up. – HappyDude Sep 16 '08 at 23:48
  • 1
    @Kevin: If you're still around, I'm wondering how certain you are about your answer that it's just a problem in the standard - at the time did you actually go through the standard to check for it? If so, I can pretty much just close this question, as this is as much an answer as I'm going to get. – HappyDude Sep 17 '08 at 00:27
  • 3
    @HappyDude: after reading the C standard, after the first 'const' all bets appear to be off, causing the compiler to believe there are const correctness issues. Kevin has the most correct reading. – user7116 Sep 17 '08 at 02:23
  • @sixlettervariables: Thank you for looking into this. – HappyDude Sep 17 '08 at 02:55
  • @HappyDude: I had someone else explain this to me first. Then I looked it up in the C draft standard (I'm too cheap to purchase a copy of the official standard). – Kevin Sep 17 '08 at 04:48
  • @Kevin: [It's](http://www.open-std.org/jtc1/sc22/wg14/www/standards) [free.](http://stackoverflow.com/questions/81656/where-do-i-find-the-current-c-or-c-standard-documents/83763#83763) –  Aug 17 '10 at 06:40
  • 2
    @Kevin C11 6.3.2.3 `For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.` What exactly is the problem with the above? These rules apply to pointers as well as pointer-to-pointers. A pointer can be a pointed-to type. – Lundin Dec 07 '15 at 10:23
  • @Lundin it seems like that invalidates this answer, or am I missing something? – Jonathan Mee Jul 16 '18 at 17:37
  • 2
    @JonathanMee Yes, the answer is incorrect and I have no idea how it got so many upvotes. – Lundin Jul 16 '18 at 21:26
  • @Lundin I do find it unusual that anyone would be able to just make a claim that the standard says anything without a citation. I guess that the C++ tag wasn't always the thunderdome that it is today. – Jonathan Mee Jul 17 '18 at 01:56
11

However, in pure C, this still gives a warning, and I don't understand why

You've already identified the problem -- this code is not const-correct. "Const correct" means that, except for const_cast and C-style casts removing const, you can never modify a const object through those const pointers or references.

The value of const-correctness -- const is there, in large part, to detect programmer errors. If you declare something as const, you're stating that you don't think it should be modified -- or at least, those with access to the const version only should not be able to modifying it. Consider:

void foo(const int*);

As declared, foo doesn't have permission to modify the integer pointed to by its argument.

If you're not sure why the code you posted isn't const-correct, consider the following code, only slightly different from HappyDude's code:

char *y;

char **a = &y; // a points to y
const char **b = a; // now b also points to y

// const protection has been violated, because:

const char x = 42; // x must never be modified
*b = &x; // the type of *b is const char *, so set it 
         //     with &x which is const char* ..
         //     ..  so y is set to &x... oops;
*y = 43; // y == &x... so attempting to modify const 
         //     variable.  oops!  undefined behavior!
cout << x << endl;

Non-const types can only convert to const types in particular ways to prevent any circumvention of const on a data-type without an explicit cast.

Objects initially declared const are particularly special -- the compiler can assume they never change. However, if b can be assigned the value of a without a cast, then you could inadvertently attempt to modify a const variable. This would not only break the check you asked the compiler to make, to disallow you from changing that variables value -- it would also allow you break the compiler optimizations!

On some compilers, this will print 42, on some 43, and others, the program will crash.

Edit-add:

HappyDude: Your comment is spot on. Either the C langauge, or the C compiler you're using, treats const char * const * fundamentally differently than the C++ language treats it. Perhaps consider silencing the compiler warning for this source line only.

S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
Aaron
  • 3,454
  • 23
  • 26
  • 5
    What you have said is correct, but it does not address the question. As I said, I understand why converting from a char** to a const char** is incorrect. What I don't understand is why converting from a char** to a const char* const * is incorrect. I have updated my question to elucidate. – HappyDude Sep 16 '08 at 23:15
  • 3
    It's interesting that this response keeps getting voted up. I guess a lot of people don't read past the first couple lines of the question, something I'll keep in mind for the future. Though this is quite a well written post on a relatively obscure point, I just wish it was relevant :P. – HappyDude Sep 17 '08 at 00:35
  • 3
    @Aaron: your post isn't particularly relevant to the topic, but well written nonetheless. – user7116 Sep 17 '08 at 00:49
11

To be considered compatible, the source pointer should be const in the immediately anterior indirection level. So, this will give you the warning in GCC:

char **a;
const char* const* b = a;

But this won't:

const char **a;
const char* const* b = a;

Alternatively, you can cast it:

char **a;
const char* const* b = (const char **)a;

You would need the same cast to invoke the function f() as you mentioned. As far as I know, there's no way to make an implicit conversion in this case (except in C++).

Fabio Ceconello
  • 15,819
  • 5
  • 38
  • 51
  • Please read the question more carefully. I am aware that the first code snippet is incorrect, as I say on the line immediately following it. The second one, however, should be fine (as far as I can tell). As I have mentioned, MSVC does not give a warning, but gcc does. – HappyDude Sep 17 '08 at 00:13
  • Sorry, I didn't explained well what I wanted, and also didn't test the result in GCC to make sure the code samples were accurate. Correcting. – Fabio Ceconello Sep 17 '08 at 01:24
  • Thanks Fabio, your edited response is much clearer. I think I'm just going to have to accept that I need to cast it. – HappyDude Sep 17 '08 at 01:48
2

This is annoying, but if you're willing to add another level of redirection, you can often do the following to push down into the pointer-to-pointer:

char c = 'c';
char *p = &c;
char **a = &p;

const char *bi = *a;
const char * const * b = &bi;

It has a slightly different meaning, but it's usually workable, and it doesn't use a cast.

wnoise
  • 9,764
  • 37
  • 47
0

I'm not able to get an error when implicitly casting char** to const char * const *, at least on MSVC 14 (VS2k5) and g++ 3.3.3. GCC 3.3.3 issues a warning, which I'm not exactly sure if it is correct in doing.

test.c:

#include <stdlib.h> 
#include <stdio.h>
void foo(const char * const * bar)
{
    printf("bar %s null\n", bar ? "is not" : "is");
}

int main(int argc, char **argv) 
{
    char **x = NULL; 
    const char* const*y = x;
    foo(x);
    foo(y);
    return 0; 
}

Output with compile as C code: cl /TC /W4 /Wp64 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter

Output with compile as C++ code: cl /TP /W4 /Wp64 test.c

test.c(8) : warning C4100: 'argv' : unreferenced formal parameter
test.c(8) : warning C4100: 'argc' : unreferenced formal parameter

Output with gcc: gcc -Wall test.c

test2.c: In function `main':
test2.c:11: warning: initialization from incompatible pointer type
test2.c:12: warning: passing arg 1 of `foo' from incompatible pointer type

Output with g++: g++ -Wall test.C

no output

user7116
  • 63,008
  • 17
  • 141
  • 172
  • Sorry, I should have clarified that I'm compiling with gcc. I have updated my question to clarify this. Fortunately (or perhaps not?) I rarely run into many difficult-to-handle warnings in msvc. – HappyDude Sep 16 '08 at 23:46
  • @HappyDude: I've updated with my findings from GCC. And now I see what you're saying, but I don't agree with the compiler warning just yet. I'm going to break out my C specification and see what I find. – user7116 Sep 16 '08 at 23:48
  • @sixlettervariables: Thanks for the response! Let me know if you do find something, but I suspect that what Kevin said was correct and it just isn't covered by the standard. – HappyDude Sep 17 '08 at 00:17
0

I'm pretty sure that the const keyword does not imply the data can't be changed/is constant, only that the data will be treated as read-only. Consider this:

const volatile int *const serial_port = SERIAL_PORT;

which is valid code. How can volatile and const co-exist? Simple. volatile tells the compiler to always read the memory when using the data and const tells the compiler to create an error when an attempt is made to write to the memory using the serial_port pointer.

Does const help the compiler's optimiser? No. Not at all. Because constness can be added to and removed from data through casting, the compiler cannot figure out if const data really is constant (since the cast could be done in a different translation unit). In C++ you also have the mutable keyword to complicate matters further.

char *const p = (char *) 0xb000;
//error: p = (char *) 0xc000;
char **q = (char **)&p;
*q = (char *)0xc000; // p is now 0xc000

What happens when an attempt is made to write to memory that really is read only (ROM, for example) probably isn't defined in the standard at all.

Skizz
  • 69,698
  • 10
  • 71
  • 108
  • 3
    This doesn't really address the question. As I understand it, removing const via casting is technically an undefined operation according to the C standard, even though most compilers will allow it and 'do the right thing'. But it isn't what I'm asking about at all. – HappyDude Sep 16 '08 at 23:53
  • 3
    @HappyDude casting away const is well-defined. It would only be undefined if you actually attempt to write to an object declared as `const` (or otherwise unwritable, such as a string literal). – M.M Nov 30 '15 at 00:47