78

I know that it improves readability and makes the program less error-prone, but how much does it improve the performance?

And on a side note, what's the major difference between a reference and a const pointer? I would assume they're stored in the memory differently, but how so?

Mmmh mmh
  • 5,334
  • 3
  • 21
  • 29
slartibartfast
  • 4,348
  • 5
  • 31
  • 46
  • 8
    No, but it can give your own code room for more optimization. An example is an immutable string class providing constant time substring operations and creation from literal without dynamic allocation. There's much power in that `const`, but mostly for humans, and the code that we write; the compiler cannot trust anything and so it cannot reasonably benefit in the same way. – Cheers and hth. - Alf Jun 11 '11 at 03:38

7 Answers7

70

[Edit: OK so this question is more subtle than I thought at first.]

Declaring a pointer-to-const or reference-of-const never helps any compiler to optimize anything. (Although see the Update at the bottom of this answer.)

The const declaration only indicates how an identifier will be used within the scope of its declaration; it does not say that the underlying object can not change.

Example:

int foo(const int *p) {
    int x = *p;
    bar(x);
    x = *p;
    return x;
}

The compiler cannot assume that *p is not modified by the call to bar(), because p could be (e.g.) a pointer to a global int and bar() might modify it.

If the compiler knows enough about the caller of foo() and the contents of bar() that it can prove bar() does not modify *p, then it can also perform that proof without the const declaration.

But this is true in general. Because const only has an effect within the scope of the declaration, the compiler can already see how you are treating the pointer or reference within that scope; it already knows that you are not modifying the underlying object.

So in short, all const does in this context is prevent you from making mistakes. It does not tell the compiler anything it does not already know, and therefore it is irrelevant for optimization.

What about functions that call foo()? Like:

int x = 37;
foo(&x);
printf("%d\n", x);

Can the compiler prove that this prints 37, since foo() takes a const int *?

No. Even though foo() takes a pointer-to-const, it might cast the const-ness away and modify the int. (This is not undefined behavior.) Here again, the compiler cannot make any assumptions in general; and if it knows enough about foo() to make such an optimization, it will know that even without the const.

The only time const might allow optimizations is cases like this:

const int x = 37;
foo(&x);
printf("%d\n", x);

Here, to modify x through any mechanism whatsoever (e.g., by taking a pointer to it and casting away the const) is to invoke Undefined Behavior. So the compiler is free to assume you do not do that, and it can propagate the constant 37 into the printf(). This sort of optimization is legal for any object you declare const. (In practice, a local variable to which you never take a reference will not benefit, because the compiler can already see whether you modify it within its scope.)

To answer your "side note" question, (a) a const pointer is a pointer; and (b) a const pointer can equal NULL. You are correct that the internal representation (i.e. an address) is most likely the same.

[update]

As Christoph points out in the comments, my answer is incomplete because it does not mention restrict.

Section 6.7.3.1 (4) of the C99 standard says:

During each execution of B, let L be any lvalue that has &L based on P. If L is used to access the value of the object X that it designates, and X is also modified (by any means), then the following requirements apply: T shall not be const-qualified. ...

(Here B is a basic block over which P, a restrict-pointer-to-T, is in scope.)

So if a C function foo() is declared like this:

foo(const int * restrict p)

...then the compiler may assume that no modifications to *p occur during the lifetime of p -- i.e., during the execution of foo() -- because otherwise the Behavior would be Undefined.

So in principle, combining restrict with a pointer-to-const could enable both of the optimizations that are dismissed above. Do any compilers actually implement such an optimization, I wonder? (GCC 4.5.2, at least, does not.)

Note that restrict only exists in C, not C++ (not even C++0x), except as a compiler-specific extension.

Community
  • 1
  • 1
Nemo
  • 70,042
  • 10
  • 116
  • 153
  • +1 for the 1st answer that at least provides some justification! – André Caron Jun 11 '11 at 03:28
  • 10
    *"So in the case of global variables -- which you should not use"* Sorry, but that's bad advice—there are many cases where global variables can and should be used. – Adam Rosenfield Jun 11 '11 at 03:42
  • I see, so `const` actually has little to nothing to do with how the variable is actually stored in memory. – slartibartfast Jun 11 '11 at 03:46
  • @Adam: OK, I changed it to "which you usually should avoid" :-). @myrkos: For a global, it might cause the compiler to put the variable in a read-only section. But for function arguments or locals, no. – Nemo Jun 11 '11 at 03:53
  • It seems that `const` class members behave similarly to global variables in this regard. A smart compiler theoretically can prove useful things about some pointers to `const` too. – n. m. could be an AI Jun 11 '11 at 03:58
  • 4
    Modifying _any_ `const` variable via `const_cast` invokes UB, not just global `const`s... – ildjarn Jun 11 '11 at 04:02
  • @ildjarn: OK, that is a fair point. I have rewritten my answer; see what you think. – Nemo Jun 11 '11 at 04:39
  • 1
    @Billy: the answer might be correct as far as plain pointers-to-`const` are concerned, but it's at least incomplete - Nemo forgot about `restrict`: declaring `foo()` as `int foo(const int *restrict p)` would indeed tell the compiler that the value pointed to by `p` won't be changed by an invocation of `foo()` – Christoph Jun 11 '11 at 07:26
  • @Christoph: I don't see how that's the case. `restrict` means that you can't have an aliased pointer, not that someone can't `const_cast` something away. – Billy ONeal Jun 11 '11 at 07:32
  • @Billy, @Cristoph: `const_cast(p)` *creates* an alias of `p`. `restrict` says that there aren't any aliases of `p`. I believe it's legal for the code in the scope of a `restrict` pointer to create such aliases with certain restrictions, but that's pretty much the most complicated section of the C99 standard, and it's 9am here, so I'm not going to dive in to prove it. – Steve Jessop Jun 11 '11 at 08:08
  • 6
    @Billy: re-read C99 section 6.7.3.1: what `restrict` actually means is that (1) if an object pointed-to by a `restrict`-qualified pointer is modified, all access (including the one which triggered the modification) must be based on that pointer, and (2) - the part which is relevant here - if any modification happens, the pointer must not be `const`-qualified; bottom line: it is illegal to modify objects pointed to by `restrict`-qualified pointers-to-`const` during the lifetime of that pointer – Christoph Jun 11 '11 at 08:38
  • @Billy: I think @Christoph is right. I have added an update to my answer. – Nemo Jun 11 '11 at 13:18
  • I'm curious, when you say _"GCC 4.5.2, at least, does not"_, how do you know? Could you provide some sort of proof? I'm asking because [this older Stack Overflow answer](http://stackoverflow.com/a/1966649/1383051) seems to show some sort of (small) improvement. Just curious :) – Anish Ramaswamy Feb 19 '14 at 00:43
  • 1
    @AnishRamaswamy: I do not doubt that "restrict" makes a difference in some cases because it helps with alias analysis... What it does not do is enable this particular optimization of propagating the constant across the call. I know because I tried it and looked at the assembly code. (And I just tried again with GCC 4.8.1; same result.) – Nemo Feb 19 '14 at 01:03
  • You say "Even though foo() takes a pointer-to-const, it might cast the const-ness away and modify the int. (This is not undefined behavior.)" Which surprises me ... can you elaborate? – JCx Jun 25 '15 at 07:11
  • @SteveJessop "_const_cast(p) creates an alias of p_" so does `*p` as soon as `p` is converted to an rvalue (lol) – curiousguy May 18 '18 at 18:15
7

Off the top of my head, I can think of two cases where proper const-qualification allows additional optimizations (in cases where whole-program analysis is unavailable):

const int foo = 42;
bar(&foo);
printf("%i", foo);

Here, the compiler knows to print 42 without having to examine the body of bar() (which might not be visible in the curent translation unit) because all modifications to foo are illegal (this is the same as Nemo's example).

However, this is also possible without marking foo as const by declaring bar() as

extern void bar(const int *restrict p);

In many cases, the programmer actually wants restrict-qualified pointers-to-const and not plain pointers-to-const as function parameters, as only the former make any guarantees about the mutability of the pointed-to objects.

As to the second part of your question: For all practical purposes, a C++ reference can be thought of as a constant pointer (not a pointer to a constant value!) with automatic indirection - it is not any 'safer' or 'faster' than a pointer, just more convenient.

Community
  • 1
  • 1
Christoph
  • 164,997
  • 36
  • 182
  • 240
  • The compiler can propagate value of a nonconst object if it's passed by const reference: due to §5.2.2/5 it can silently create a temporary copy of the object passed. – Ruslan Apr 09 '16 at 15:49
6

There are two issues with const in C++ (as far as optimization is concerned):

  • const_cast
  • mutable

const_cast mean that even though you pass an object by const reference or const pointer, the function might cast the const-ness away and modify the object (allowed if the object is not const to begin with).

mutable mean that even though an object is const, some of its parts may be modified (caching behavior). Also, objects pointed to (instead of being owned) can be modified in const methods, even when they logically are part of the object state. And finally global variables can be modified too...

const is here to help the developer catch logical mistakes early.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • If a function casts away const using `const_cast` and then modifies the object, it's undefined behaviour; compilers can safely assume that case never occurs. – James Picone Jan 09 '18 at 06:28
  • 1
    @JamesPicone: Yes and no. It's undefined behavior to modify a `const` object, but it's the reason-d'etre of `const_cast` to cast away the `const` attribute of a reference or pointer to modify a non-`const` object. So the compiler can extract some performance gains from `const`, just a fraction of what it could be without `const_cast`. – Matthieu M. Jan 09 '18 at 07:00
4

const-correctness generally doesn't help performance; most compilers don't even bother to track constness beyond the frontend. Marking variables as const can help, depending on the situation.

References and pointers are stored exactly the same way in memory.

servn
  • 3,049
  • 14
  • 8
  • 2
    Can you provide an example where it helps (or ought to)? Saying "doesn't help", "can help" and "depending on the situation" all in the same paragraph doesn't really clarify the situation. – André Caron Jun 11 '11 at 03:09
  • const helps with constructs like the following (and more complicated variants): int x = 10; int f() { return x; } vs. const int x = 10; int f() { return x; } – servn Jun 16 '11 at 03:32
2

This really depends on the compiler/platform (it may help optimisation on some compiler that has not yet been written, or on some platform that you never use). The C and C++ standards say nothing about performance other than giving complexity requirements for some functions.

Pointers and references to const usually do not help optimisation, as the const qualification can legally be cast away in some situations, and it is possible that the object can be modified by a different non-const reference. On the other hand, declaring an object to be const can be helpful, as it guarantees that the object cannot be modified (even when passed to functions that the compiler does not know the definition of). This allows the compiler to store the const object in read-only memory, or cache its value in a centralised place, reducing the need for copies and checks for modifications.

Pointers and references are usually implemented in the exact same way, but again this is totally platform dependant. If you are really interested then you should look at the generated machine code for your platform and compiler in your program (if indeed you are using a compiler that generates machine code).

Mankarse
  • 39,818
  • 11
  • 97
  • 141
1

One thing is, if you declare a global variable const, it may be possible to put it in the read-only portion of a library or executable and thus share it among multiple processes with a read-only mmap. This can be a big memory win on Linux at least if you have a lot of data declared in global variables.

Another situation, if you declare a constant global integer, or float or enum, the compiler may be able to just put the constant inline rather than using a variable reference. That's a bit faster I believe though I'm not a compiler expert.

References are just pointers underneath, implementation-wise.

Havoc P
  • 8,365
  • 1
  • 31
  • 46
  • Same deal as the other answers I -1'd -- if it's truly never written to in the entire program, the compiler is going to be able to perform these kinds of optimizations whether it's marked `const` or not. – Billy ONeal Jun 11 '11 at 05:34
  • 1
    @Billy: not for `extern` variables. – Matthieu M. Jun 11 '11 at 08:56
  • @Matthieu: He is referring specifically to whole program optimization, performed at link time when `extern` stops meaning much. – Dennis Zickefoose Jun 11 '11 at 09:09
  • @Dennis: ah right, sorry, I am used to deal with DLLs, where WPO loses much strength. – Matthieu M. Jun 11 '11 at 15:32
  • @Billy Maybe in theory, but in practice on Linux at least, you're going to be using shared libraries (or writing a shared library) and the compiler couldn't do this. What kind of program on Linux doesn't use shared libraries? Basically none of them. I'm not sure gcc does this in any case, shared libraries or no - if it does, it's a pretty recent feature. – Havoc P Jun 11 '11 at 18:47
  • @Havoc: Shared libraries work with LTCG just fine. Besides -- if a `const` is exposed outside of your module in any way shape or form, the compiler can no longer make assumptions about it, const qualified or no. Who says the other library has to be written in a language with `const`? If the other module would be written in assembler and modifies data where a pointer points, the compiler can't control that. And it would not be invoking UB because the other code would not be C or C++. Most of the rules C and C++ impose on you don't exist in assembly land. – Billy ONeal Jun 11 '11 at 20:59
  • I think the problem may be you're talking Windows and I'm talking Linux/ELF. On Linux, if you have a shared/extern symbol in a shared library that's say a big array of const structs, then that will go in the read-only section of the shared lib and be mmap'd read-only. If another module in assembler or something tried to modify it, then it would segfault. The read-only data is shared among all processes using that shared library. Windows DLLs are certainly a different deal, and I can't tell you much about them. But on Linux there is a memory advantage to declaring data const, I promise. – Havoc P Jun 11 '11 at 22:21
0

It can help performance a little bit, but only if you are accessing the object directly through its declaration. Reference parameters and such cannot be optimized, since there might be other paths to an object not originally declared const, and the compiler generally can't tell if the object you are referencing was actually declared const or not unless that's the declaration you are using.

If you are using a const declaration, the compiler will know that externally-compiled function bodies, etc. cannot modify it, so you get a benefit there. And of course things like const int's are propagated at compile time, so that's a huge win (compared to just an int).

References and pointers are stored exactly the same, they just behave differently syntactically. References are basically renamings, and so are relatively safe, whereas pointers can point to lots of different things, and are thus more powerful and error-prone.

I guess the const pointer would be architecturally identical to the reference, so the machine code and efficiency would be the same. the real difference is syntax -- references are a cleaner, easier to read syntax, and since you don't need the extra machinery provided by a pointer, a reference would be stylistically preferred.

d00t
  • 19
  • 1
  • If the item is not modified, decent compilers nowadays are going to know that it's `const` just fine without you telling it so. (Speaking of recent MSVC++ and GCCs, both have link time code generation) – Billy ONeal Jun 11 '11 at 05:33
  • 1
    @Billy: link-time optimizations are no magic cure-all because all libraries also would have to be compiled with lto enabled, not to mention shared libraries - the compiler can't reason about code it never gets to see... – Christoph Jun 11 '11 at 09:40
  • @Christoph: The compiler can't ever trust `const` qualification about variables it can't see either. That memory may be messed with by completely different languages, where `const` does not exist, or it could even be written in raw assembler. In such cases where a `const` variable is made accessible to that library, the compiler has to disregard the const qualification. Therefore I don't see how that's relevant to this question. – Billy ONeal Jun 11 '11 at 15:46