2

As a follow up to this question: How do you specify variables to be final in C?, I've been playing around with const variables. In particular, I've written and compiled the following program:

#include<stdio.h>

int main(void) {
    const int i = 5;
    int* i_ptr = &i;

    *i_ptr = *i_ptr + 1;      // (1)

    printf("%d %d\n", *i_ptr, i); 

    int j = 10;
    const int* j_ptr = &j;
    
    j_ptr = i_ptr;       // (2)
    
    printf("%d %d\n", *j_ptr, j);

    return 0;
}

I'm working with the following gcc compiler:

$ gcc --version 
Apple LLVM version 8.1.0 (clang-802.0.42) 
Target: x86_64-apple-darwin16.5.0 
Thread model: posix

In the code listing, I've marked two lines of code that behave differently than I'd expect:

  1. (1) I would expect the compiler to spit at me for trying to modify the const variable i that i_ptr but to my surprise it does not.
  2. (2) I would expect another error to be generated here because j_ptr has just been declared const and now modifying it is perfectly okay.

Further more, here is the output I get:

6 5
6 10

What's surprising here is that, in the first line out output, i_ptr and i do not have the same values here. What gives? I certainly didn't call malloc and assign i_ptr to new memory.

So how is it pointing to something besides i?

What's even more puzzling is that, if I replace line (2) with this:

*j_ptr = *j_ptr + 1; 

I get

error: read-only variable is not assignable

I'd expect that, since j_ptr is pointing to non-const variable j, it should increment j. But that doesn't happen.

I'd really appreciate if someone could explain my observations, as I can't seem to come up with any reasons for why these const pointers should behave this counter-intuitively.

Community
  • 1
  • 1
cs95
  • 379,657
  • 97
  • 704
  • 746
  • @DavidBowling It didn't modify though... it created a new variable different from the original. – cs95 Jun 08 '17 at 13:34
  • 3
    With `int* i_ptr = &i;` My compiler reported "error: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]". What compiler are you using? – chux - Reinstate Monica Jun 08 '17 at 13:34
  • Agreed, `int* i_ptr = &i;` shouldn't compile, for the reason you mentioned. – Quentin Jun 08 '17 at 13:35
  • @chux `gcc --version` gave me this: Apple LLVM version 8.1.0 (clang-802.0.42) Target: x86_64-apple-darwin16.5.0 Thread model: posix – cs95 Jun 08 '17 at 13:36
  • Try `gcc -std=c11 ...` or `-std=c99` – chux - Reinstate Monica Jun 08 '17 at 13:38
  • 1
    @chux Just tried both as per your suggestion. Only a warning, but it still compiles in both cases and gives the same output. – cs95 Jun 08 '17 at 13:41
  • 1
    I'd expect `const int i = 5; int* i_ptr = &i;` to fail compiling with a C compliant compiler. IMO, that is a compiler bug. Yet the subtitles of C are extensive. Good luck. – chux - Reinstate Monica Jun 08 '17 at 13:43
  • @chux where you get an error, I get a warning: `test.c:5:7: warning: initializing 'int *' with an expression of type 'const int *' discards qualifiers` – cs95 Jun 08 '17 at 13:44
  • I get only a warning with `gcc -std=c99 -Wall -Wextra -Wpedantic`. By changing to `int *i_ptr = (int *)&i;` I get no warnings at all. But still UB, as mentioned by [@SouravGhosh](https://stackoverflow.com/a/44437233/6879826) below. – ad absurdum Jun 08 '17 at 13:47
  • Also wondering why I'm being downvoted. – cs95 Jun 08 '17 at 13:56
  • Thank you. I appreciate the support. There's just one more thing these excellent answers do not explain. That is the meaning behind the outputs of the first `printf`. – cs95 Jun 08 '17 at 14:21
  • 1
    @Shiva-- when I run your code, I get `6 6` for the first line of output. You are reporting `6 5`. Both are irrelevant; the program is meaningless after undefined behavior occurs. – ad absurdum Jun 08 '17 at 14:28
  • 1
    It is also meaningless before it happens, if it has UB. Compilers might reorder stuff, for example. As to the 6 5, your compiler probably produces code where the value of i is kept in a register throughout the function, since it is const and so should not change. – Ilja Everilä Jun 08 '17 at 14:40
  • If your compiler doesn't complain about that, turn on more warning flags. On gcc or clang, I personally use: `-std=c11 -Wall -Wextra -Wpedantic -Wconversion`. Some programs might need something like `c89` or `gnu11` instead of `c11`. – Davislor Jan 12 '19 at 03:58

3 Answers3

3

Quoting C11, chapter §6.7.3

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. [...]

So, the syntax is not forbidden as per the standard, just doing so causes undefined behavior. Any compiler, with sufficient level of warnings enabled should scream at the assignments which attempts to discards the const qualifier.

Secondly, in case of const int* j_ptr = &j;, the const is applicable for the pointed type, not the value of the variable. Thus, you can change j_ptr, but not *j_ptr.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
  • So `i_ptr` pointing to a different value than `i` after the assignment is a part of that undefined behaviour? Is it a security measure by the compiler? – cs95 Jun 08 '17 at 14:17
  • @Shiva 1) Yes 2) it's more of a case attached to MMU or OS, not the compiler. – Sourav Ghosh Jun 08 '17 at 14:38
  • Thank you for your help. I only wish I could mark multiple answers. Your answer and tilz0R's answer cover different parts of my question but tilz0R's covers more. – cs95 Jun 08 '17 at 14:46
  • @Shiva Sure, no problems, the acceptance mark indicates the helpfulness of an answer to you (OP), so please feel free to choose the answer which helped "you" the most, and you're welcome. :) – Sourav Ghosh Jun 08 '17 at 15:00
3

You can modify const variable through pointer but this is undefined behaviour.

This should be an error:

const int i = 5;
int* i_ptr = &i;  //This is error!

While this shouldn't be:

const int i = 5;
const int* i_ptr = &i;  //This is ok
*i_ptr = 4;    //This is error, you cannot write to pointer if it is expressed as const

In second type, it means that you cannot write via i_ptr variable.

If const int a = 5 means that it is constant variable and can't be modified, const int* a_ptr means that variable points to some constant value but variable a_ptr itself is not constant and can be modified.


In some functions, you may see this function structure:

void funcname(const int* arr, size_t size) {

}

Please focus on const part of function parameter. This const means for this function that code can not be modified via arr pointer, but only read can be done.

Example function to read data from array:

void funcname(const int* arr, size_t size) {
    while (size--) {
        a = *arr;        //You can read
        *arr = *arr * 2; //You cannot update it! 
        arr++            //You can do that as you modify where pointer points not value on location where it points.
    }
}

But if you want to prevent that also pointer where it points can't be modified, you can do this using below function. Check first parameter with double const.

void funcname(const int* const arr, size_t size) {
    while (size--) {
        a = *arr;        //You can read
        *arr = *arr * 2; //You cannot update it! 
        arr++            //Error! You cannot even increase pointer because of second const keyword in parameter
    }
}

Please check this too: http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html

unalignedmemoryaccess
  • 7,246
  • 2
  • 25
  • 40
  • +1, this was extremely instructive. The only thing this question doesn't answer is why `*i_ptr` and `i` prints out 6 and 5 respectively. – cs95 Jun 08 '17 at 14:18
2

Also note the place you put const to.

If you do:

int i = 0;
const int * ptr_to_const_value = &i;
int * const const_ptr = &i;
ptr_to_const_value = &j // this is ok
const_ptr = &j; // this does not compile

To ptr_to_const_value you can assign pointer at any time, but you cannot modify value this pointer points to.

On the other hand, const_ptr cannot be reassigned, but you can modify the value it points to.

nowaqq
  • 261
  • 3
  • 10