8

Please consider the following code:

void func1(const int &i);
void func2(int i);

void f()
{
  int a=12;
  func1(a);
  func2(a);
}

Compiled with g++ 4.6 with -O3, I can see that the compiled re-reads the value of "a" between the function calls. Changing a's definition to "const int", the compiler doesn't do that, and instead simply loads immediate value "12" into edi. The same is true if a is not const, but I change func1's signature to accept by value.

While not a bug in the code generation, this is still weird behavior. Being as it is that func1 made a commitment not to change a, why should the compiler's code change based on whether a is const or not?

Edit: Some skeptics claim that I might be reading the code wrong. The above code produces the following with -S compilation:

_Z1fv:
.LFB0:
        .cfi_startproc
        subq    $24, %rsp
        .cfi_def_cfa_offset 32
        leaq    12(%rsp), %rdi
        movl    $12, 12(%rsp)                                                          
        call    _Z5func1RKi
        movl    12(%rsp), %edi     <-- Rereading a
        call    _Z5func2i
        addq    $24, %rsp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc                                                           

Changing a to const produces:

_Z1fv:
.LFB0:
        .cfi_startproc
        subq    $24, %rsp
        .cfi_def_cfa_offset 32
        leaq    12(%rsp), %rdi
        movl    $12, 12(%rsp)
        call    _Z5func1RKi
        movl    $12, %edi          <-- Use immediate value
        call    _Z5func2i
        addq    $24, %rsp
        .cfi_def_cfa_offset 8
        ret
        .cfi_endproc
quetzalcoatl
  • 32,194
  • 8
  • 68
  • 107
Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57
  • `func1` *could* `const_cast` the const-ness away and write to `i`, and it's actually legal (i.e., not UB) since the underlying object, `a`, isn't declared `const`. – T.C. Jul 30 '14 at 05:42
  • How can func1 know whether a is declared const or not? – Shachar Shemesh Jul 30 '14 at 05:44
  • It can't; it just causes undefined behavior if it is called with something that is actually declared const. – T.C. Jul 30 '14 at 05:46
  • `func1` itsef knows nothing. It's a bunch of characters in a text file. The compiler knows what `a` is when calling the function. – Arne Mertz Jul 30 '14 at 05:46
  • You should compile with `-S` and post the assembly... not tell us your interpretation of the code and let us grope around in the dark. This is particularly important as your description is suspect... calling `func1(const int&)` implies a need to get 2 on to the stack and pass the address, where `func2(a)` implies a need to actually "read" the 2 and pass it as an argument, so when you say "re-read... between", perhaps it's meaningfully the *first* read. – Tony Delroy Jul 30 '14 at 07:29
  • Thanks for taking the time to post the code (and to call me a skeptic ;-P). As I said, `movl 12(%rsp), %edi` is not "rereading" the value... it's the first and only read in the client code, following a calculation of the stack address (`leaq`) and a write (`movl $12,...`). Trivial distinction, but I wanted to make sure you weren't seeing two reads which would have been weird. All that not-withstanding, why only the second has `movl $12, %edi` is a good question... (and has been answered well) +1. Cheers. – Tony Delroy Jul 31 '14 at 06:28

2 Answers2

6

In C++, const is really just logical constness and not physical constness. func1 can do a const_cast and modify i. const is like the safety of a gun - you can still shoot yourself in the foot, but not by accident.

As T.C. and juanchopanza have pointed out in the comments, casting away the constness of an object and modifying it is UB. Quoting from "Notes" here :

Even though const_cast may remove constness or volatility from any pointer or reference, using the resulting pointer or reference to write to an object that was declared const or to access an object that was declared volatile invokes undefined behavior.

Pradhan
  • 16,391
  • 3
  • 44
  • 59
  • But the same is true if I declare "a" const, and yet the compiler does allow itself to optimize away the second memory read. – Shachar Shemesh Jul 30 '14 at 05:46
  • @ShacharShemesh Because in that case the modification would cause undefined behavior, meaning the compiler is free to do whatever it wants. – T.C. Jul 30 '14 at 05:47
  • It us UB to cast away the constness and modify a `const` object. It is irrelevant whether this is done via a pointer or a reference. – juanchopanza Jul 30 '14 at 05:58
  • In other words, your last sentence is quite misleading. – juanchopanza Jul 30 '14 at 06:47
  • @juanchopanza this means that the answer is incorrect. If it is undefined to cast away constness, then it is also undefined when func1 does it. It is legal for the compiler to assume func1 doesn't change a. – Shachar Shemesh Jul 30 '14 at 07:02
  • @ShacharShemesh Well, yes, I would say it is incorrect, but easily fixable, if the author reads the link they included carefully :-) Although note that it is not UB if func casts away the constness of something that isn't really `const`. What matters is the constness of the object being referred/pointer to (similarly for volatile-ness.) – juanchopanza Jul 30 '14 at 07:08
3

Summing up the answers, I think this explains it best:

It is legal to take a const reference to a non-const variable, and then cast away the constness. Therefore, the compiler in the first case cannot assume that func1 will not change a.

It is undefined what happens if you cast away the constness to a variable declared const. The compiler in the second case may assume that func1 will not cast away the constness. If func1 does cast away the constness, func2 will receive the "wrong" value, but that's just one consequence of undefined behaviour.

Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57