2

Why is this fine:

char a[2];
a[0]='a';
const char* b;
b=a;
printf("%s\n",b);
a[0]='b';
printf("%s",b);

why can a pointer to a constant string point to a non constant string? Also how do string constants work when assigning them to variables? Why can you do this:

const char* b="hello";
b="test";

or

char* b="hello";
b="test";

but you can only do the first line if it is an array? constant or not?

RSA
  • 21
  • 1
  • 1
    *"why can a pointer to a constant string point to a non constant string?"* Why not? The opposite would be dangerous, yes, so it's not allowed without a cast. `const char* b="hello";` You can't modify strings literals (it causes UB), but they are not constant in the sense that their type has no `const` in it. – HolyBlackCat Feb 20 '20 at 21:11
  • 1
    `const char *b` means you can't change the string contents through `b`, but you can still reassign `b`. To make `b` constant you have to write `const char * const b` – Barmar Feb 20 '20 at 21:16

2 Answers2

3

Why can I have a const char * point to a mutable char array?

The const here with b puts a restriction on its use. It is not allowing some new usage, but potentially less. So no opening of Pandora's Box here.

char a[2];
const char* b = a;

b has read-access to the array. The array a[] may still change via a.

a[0]='y'; // OK
b[0]='z'; // leads to error like "error: assignment of read-only location "

Also how do string constants work when assigning them to variables? constant or not?

"hello" is a string literal of type char [6]. With const char* b1 = "hello" declaration and initialization, b1 is assigned a pointer of type char *. This could have been const char *, yet for historical reasons1 it is char *. Since it is char*, char* b2 = "hello"; is also OK.

const char* b1 = "hello";
char* b2 = "hello";

Also how do string constants work when assigning them to variables?

b="test";

As part of the assignment, the string literal, a char array, is converted to the address and type of its first element, a char *. That pointer is assigned to b.


1 const was not available in early C, so to not break existing code bases, string literals remained without a const.

chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • If what you say is true, then sizeof ("hello") == 6? If that is the case, then today I re-learned that C never ceases to surprise me. Just when you think you know something... – jdc Feb 20 '20 at 21:42
  • However, isn't it also true that modifying a string literal in C is undefined behavior? That is, `char *b = "hello"; strcpy(b, "world");` leads to undefined behavior? Maybe that's why I've got it in my head that "hello" is a `const char*` type. – jdc Feb 20 '20 at 21:50
  • 1
    @jdc Yes, True. It is good to think of a _string literal_ as a `const char` array even though it does not have that `const`-ness in it type, just in its application. – chux - Reinstate Monica Feb 20 '20 at 21:53
  • 1
    @jdc Now a little secret: `((char *)b)[0]='z';` is OK. It is OK to cast away `const`-ness and use that pointer `(char *)b` to write **if** the array pointed to is not `const` (and not a _string literal_), as in OP's case. Yet casting away `const`-ness can lead to UB in other cases, so wise to avoid. Oh, I see [Eric](https://stackoverflow.com/questions/60328496/why-can-i-have-a-const-char-pointer-point-to-a-mutable-char-array/60328919#comment106717973_60328834) has that. – chux - Reinstate Monica Feb 20 '20 at 21:58
  • That's another good point my answer failed to address - distinguishing between variables declared as `const` versus treating a non-`const` type as `const` inside a function. Since there is no portable way to distinguish between a `const` variable versus treating one as `const` in a function, I would avoid casting it away (or, send out a department-wide email letting my coworkers know where I live when that assumption is violated ;)) – jdc Feb 20 '20 at 22:04
  • @jdc "Since there is no portable way to distinguish between a const variable versus treating one as const in a function" --> Hmmm, It is not variable `const`-ness, but the `const`-ness of what it points to this post discusses. With that, I think one _can_ distinguish with `_Generic`, yet that is a whole new question with various caveats. – chux - Reinstate Monica Feb 20 '20 at 22:09
0

You can always add const-ness to a type. This is a good thing, because it allows you to write functions that make some guarantees.

//Here we know *str is const, so the function will not change what's being pointed to.
int strlength(const char *str) {
    int length = 0;
    while (*str++)
        ++length;
    return length;
}

int main(void) {
    char a[2] = "a"; //a[0] and a[1] are mutable in main(), but not in strlength().
    printf("%d", strlength(a));
}

Note that casting away const-ness leads to undefined behavior:

void capitalize(char *str) {
    if (isalpha(*str))
        *str = toupper(*str);
}

int main(void) {
    const char *b = "hello";
    capitalize((char*) b); //undefined behavior. Note: without the cast, this may not compile.
}

As for your second question, your first example is correct because the type of a string literal in C (i.e. any sequence of characters between double quotes) is of type const char*. This is valid:

const char *b = "hello"; //"hello" is of type const char*
b = "text"; //"text" is of type const char*

Because casting away const leads to undefined behavior, this code is invalid:

char *b = "hello"; //undefined behavior; "hello" is const char*
b = "text"; //undefined behavior; "text" is const char*

The case for arrays is a little more involved. When used in expressions, arrays act as pointers, but arrays are a fundamentally different type than pointers:

char a[10];
a = "hello"; //does not compile - "hello" is a const char*; a is a char[10]

However, when used in an initialization statement, the rules state that a const char* can be used to initialize a character array:

char a[10] = "hello"; //initialization - a is a char[10], "hello" is a const char*
                      //a will contain {'h','e','l','l','o',0,0,0,0,0}

Also, don't forget that you can assign a string literal to an array with strcpy:

char a[10];
strcpy(a, "hello");
assert(strcmp(a, "hello") == 0);
//a will contain {'h','e','l','l','o',0,x,x,x,x}
//here x's mean uninitialized
jdc
  • 680
  • 1
  • 8
  • 11
  • In C, passing a pointer to a `const`-qualified type is not a guarantee it will not be changed. It is an advisory and a convenience (because the compiler will warn if an attempt to modify is made with a `const`-qualified lvalue). If the pointed-to object was not defined with `const`, a `const` pointer to it may be converted to a non-`const` pointer and used to modify it. – Eric Postpischil Feb 20 '20 at 21:35
  • I agree with you that it is not a guarantee it will not be changed, but casting away const even inside the function that accepts a pointer to a const-qualified type still leads to undefined behavior. With undefined behavior, doing anything or nothing at all would not violate the standard. – jdc Feb 20 '20 at 21:37
  • 2
    No, casting away `const` and using it to access an object that was not originally defined with `const` has defined behavior. The part of the C standard that makes attempting to modify an object defined with `const` not defined by the standard is C 2018 6.7.3 7: “If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined…” There is no similar text for objects not defined with `const`, except for string literals. This is because const was grafted onto C late, so it is a bit of a kludge. – Eric Postpischil Feb 20 '20 at 21:52
  • Thank you for the reference. Still, when I write C, I look at casting away `const` inside a function as a red flag, and would try to avoid it if possible. There really is no good way around it in the case of `strchr`. – jdc Feb 20 '20 at 21:56