26

Possible Duplicate:
Sell me on const correctness

What is the usefulness of keyword const in C or C++ since it's allowed such a thing?

void const_is_a_lie(const int* n)
{ 
    *((int*) n) = 0;
}

int main()
{
    int n = 1;
    const_is_a_lie(&n);
    printf("%d", n);
    return 0;
}

Output: 0

It is clear that const cannot guarante the non-modifiability of the argument.

Community
  • 1
  • 1
gliderkite
  • 8,828
  • 6
  • 44
  • 80

5 Answers5

45

const is a promise you make to the compiler, not something it guarantees you.

For example,

void const_is_a_lie(const int* n)
{ 
    *((int*) n) = 0;
}

#include <stdio.h>
int main()
{
    const int n = 1;
    const_is_a_lie(&n);
    printf("%d", n);
    return 0;
}

Output shown at http://ideone.com/Ejogb is

1

Because of the const, the compiler is allowed to assume that the value won't change, and therefore it can skip rereading it, if that would make the program faster.

In this case, since const_is_a_lie() violates its contract, weird things happen. Don't violate the contract. And be glad that the compiler gives you help keeping the contract. Casts are evil.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • +1 for explaining the orientation of the promise made. – R.. GitHub STOP HELPING ICE Jun 06 '12 at 00:17
  • Note that this program is only incorrect in C++ - in C the only error is if you actually attempt to modify an object declared `const`. In C, if a function like `const_is_a_lie()` can determine through other information that the passed pointer doesn't actually point to a `const` object in some case, it's allowed to cast away the `const` and modify the pointed-to object. (The question is tagged with both C and C++ so I think this is relevant). – caf Jun 06 '12 at 00:22
  • @caf: The rule is the same in C++. I made the object `const` in my version of the code. – Ben Voigt Jun 06 '12 at 00:28
  • 1
    In particular, a C compiler could legally (and reasonably) conclude that `n` never changes (its local to `main`, never modified in `main`, and no non-const references escape from `main`), and so constant-fold the `printf` to print `1` even if `n` is not `const` – Chris Dodd Jun 06 '12 at 00:35
  • 3
    @ChrisDodd: No, I don't think it could. The fact that the reference that escapes is `const`-qualified is not relevant - a reference to a non-const object escapes, and that's all that matters. Do you have a reference in the standard to back up your view? – caf Jun 06 '12 at 00:50
  • 5
    It is not UB to cast away constness on a reference to a non-const object, so I'm not sure if this is actually true. – Puppy Jun 06 '12 at 00:51
  • 5
    @DeadMG In fact, it's never UB to cast away constness. It's only UB to *modify* a const object. – GManNickG Jun 06 '12 at 01:05
  • 1
    It's not just a promise to the compiler. It also useful as implicity documentation: "will this method return a copy or the actual buffer? Oh the return is const, so this must not be a copy, and I should not delete it later". – fbafelipe Jun 06 '12 at 01:20
  • 3
    @fbafelipe: `const` is often useful as a form of documentation... but not the example you give. If an object is returned by raw pointer, `const` or not, the caller shouldn't delete it. In modern C++ style, an object is returned as `std::unique_ptr` and carries its deleter with it, if ownership is being transferred. – Ben Voigt Jun 06 '12 at 01:46
  • Actually, the compiler can't assume it can skip rereading a variable just because it's const, because there could be another pointer that aliases it. eg foo( const int *a, int *b ) { int x = *a; *b = 2; x += *a; } it has to reread `*a` in the third statement because *b might have altered its contents if a == b. To guarantee the compiler that it doesn't have to reread a variable (that nothing aliases) you have to use `restrict`. (http://developers.sun.com/solaris/articles/cc_restrict.html) – Crashworks Jun 06 '12 at 01:48
  • @Crashworks: The compiler has a lot of tricks for aliasing analysis. If you instead had `foo( const int *a, float* b )`, the compiler can legally assume a write to `*b` does not change `a`. And locals are known not to alias parameters. But in my example, the object was created `const`, so the compiler can assume it is never changed my any means. – Ben Voigt Jun 06 '12 at 04:30
  • @BenVoigt Let me just clarify, OP's code is defined in `C`. Agree? – 2501 Nov 20 '14 at 02:41
  • @2501: Which version of C? Yes, the original code is defined, if that's the only place the function is called. However, it's bad style because it is fragile. – Ben Voigt Nov 20 '14 at 03:18
  • @BenVoigt The latest c11, ( and c99 since it probably has identical meaning( just checked and it looks the same ) ) – 2501 Nov 20 '14 at 03:22
10

In this case, n is a pointer to a constant int. When you cast it to int* you remove the const qualifier, and so the operation is allowed.

If you tell the compiler to remove the const qualifier, it will happily do so. The compiler will help ensure that your code is correct, if you let it do its job. By casting the const-ness away, you are telling the compiler that you know that the target of n is non-constant and you really do want to change it.

If the thing that your pointer points to was in fact declared const in the first place, then you are invoking undefined behavior by attempting to change it, and anything could happen. It might work. The write operation might not be visible. The program could crash. Your monitor could punch you. (Ok, probably not that last one.)

void const_is_a_lie(const char * c) {
    *((char *)c) = '5';
}

int main() {
    const char * text = "12345";
    const_is_a_lie(text);
    printf("%s\n", text);

    return 0;
}

Depending on your specific environment, there may be a segfault (aka access violation) in const_is_a_lie since the compiler/runtime may store string literal values in memory pages that are not writable.

The Standard has this to say about modifying const objects.

7.1.6.1/4 The cv-qualifiers [dcl.type.cv]

Except that any class member declared mutable (7.1.1) can be modified, any attempt to modify a const object during its lifetime (3.8) results in undefined behavior

"Doctor, it hurts when I do this!" "So don't do that."

Community
  • 1
  • 1
cdhowie
  • 158,093
  • 24
  • 286
  • 300
6

Your...

int n = 1;

...ensures n exists in read/write memory; it's a non-const variable, so a later attempt to modify it will have defined behaviour. Given such a variable, you can have a mix of const and/or non-const pointers and references to it - the constness of each is simply a way for the programmer to guard against accidental change in that "branch" of code. I say "branch" because you can visualise the access given to n as being a tree in which - once a branch is marked const, all the sub-branches (further pointers/references to n whether additional local variables, function parameters etc. initialised therefrom) will need to remain const, unless of course you explicitly cast that notion of constness away. Casting away const is safe (if potentially confusing) for variables that are mutable like your n, because they're ultimately still writing back into a memory address that is modifiable/mutable/non-const. All the bizarre optimisations and caching you could imagine causing trouble in these scenarios aren't allowed as the Standard requires and guarantees sane behaviour in the case I've just described.

Sadly it's also possible to cast away constness of genuinely inherently const variables like say const int o = 1;, and any attempt to modify them will have undefined behaviour. There are many practical reasons for this, including the compiler's right to place them in memory it then marks read only (e.g. see UNIX mprotect(2)) such that an attempted write will cause a CPU trap/interrupt, or read from the variable whenever the originally-set value is needed (even if the variable's identifier was never mentioned in the code using the value), or use an inlined-at-compile-time copy of the original value - ignoring any runtime change to the variable itself. So, the Standard leaves the behaviour undefined. Even if they happen to be modified as you might have intended, the rest of the program will have undefined behaviour thereafter.

But, that shouldn't be surprising. It's the same situation with types - if you have...

double d = 1;
*(int*)&d = my_int;
d += 1;

...have you have lied to the compiler about the type of d? Ultimately d occupies memory that's probably untyped at a hardware level, so all the compiler ever has is a perspective on it, shuffling bit patterns in and out. But, depending on the value of my_int and the double representation on your hardware, you may have created an invalid combination of bits in d that don't represent any valid double value, such that subsequent attempts to read the memory back into a CPU register and/or do something with d such as += 1 have undefined behaviour and might, for example, generate a CPU trap / interrupt.

This is not a bug in C or C++... they're designed to let you make dubious requests of your hardware so that if you know what you're doing you can do some weird but useful things and rarely need to fall back on assembly language to write low level code, even for device drivers and Operating Systems.

Still, it's precisely because casts can be unsafe that a more explicit and targeted casting notation has been introduced in C++. There's no denying the risk - you just need to understand what you're asking for, why it's ok sometimes and not others, and live with it.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
2

The type system is there to help, not to babysit you. You can circumvent the type system in many ways, not only regarding const, and each time that you do that what you are doing is taking one safety out of your program. You can ignore const-correctness or even the basic type system by passing void* around and casting as needed. That does not mean that const or types are a lie, only that you can force your way over the compiler's.

const is there as a way of making the compiler aware of the contract of your function, and let it help you not violate it. In the same way that a variable being typed is there so that you don't need to guess how to interpret the data as the compiler will help you. But it won't baby sit, and if you force your way and tell it to remove const-ness, or how the data is to be retrieved the compiler will just let you, after all you did design the application, who is it to second guess your judgement...

Additionally, in some cases, you might actually cause undefined behavior and your application might even crash (for example if you cast away const from an object that is really const and you modify the object you might find out that the side effects are not seen in some places (the compiler assumed that the value would not change and thus performed constant folding) or your application might crash if the constant was loaded into a read-only memory page.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
1

Never did const guarantee immutability: the standard defines a const_cast that allows modifying const data.

const is useful for you to declare more intent and avoid changing data that is you meant to be read only. You'll get a compilation error asking you to think twice if you do otherwise. You can change your mind, but that's not recommended.

As mentionned by other answers the compiler may optimize a bit more if you use const-ness but the benefits are not always significant.

J.N.
  • 8,203
  • 3
  • 29
  • 39
  • 2
    Just for anyone reading this, `const_cast` exists in C++ only, not C. – cdhowie Jun 06 '12 at 00:07
  • Oh, indeed, I missed the "C" tag. – J.N. Jun 06 '12 at 00:12
  • 1
    `const_cast` simply removes the cv-qualifier. The standard still states that "modifying a const object during its lifetime results in undefined behavior". Nowhere that I am aware of does it state removing the cv-qualifier releases the object for modification. – Captain Obvlious Jun 06 '12 at 00:21
  • @ChetSimpson Hum ... I don't have the standard nor the time to search it precisely, but I think that's "may" trigger UB, not a "will". http://msdn.microsoft.com/en-us/library/bz6at95h(v=vs.100).aspx . – J.N. Jun 06 '12 at 00:54
  • @ChetSimpson after some thinking I **think** you may modify a `const` *object* but not`const` *data*. I.E. an object can be `const` and have a `mutable` field or be `const_cast`. A constant (as in `const int i = 3`) must never be modified. There may be more precise conditions on how to do that, but I don't have time to dig more. – J.N. Jun 06 '12 at 01:02
  • @J.N. 7.1.6.1/4. The only way for it to not be UB is if the object was originally declared with mutability. (I was wrong in my other comment about having UB from the assignment). If you apply that to only const_cast then yes it _might_ produce undefined behavior. If however you apply it to a const qualified object it always results in UB. We're likely saying the same thing just from different perspectives ;) – Captain Obvlious Jun 06 '12 at 01:25
  • @ChetSimpson: I'm not sure I'm following you guys, but to clarify: `const_cast` *never* causes UB. – GManNickG Jun 06 '12 at 02:02
  • @GManNickG That's what I was referring to being wrong about in my earlier comment to K-Ballo. – Captain Obvlious Jun 06 '12 at 02:11