Handmade Hero»Forums»Code
Gafgar (Anders Davallius)
9 posts
Programmer and Creative Director at Coilworks.
Constpiracy
Edited by Gafgar (Anders Davallius) on
I agree, const is not hard to go around... it's not real meant for security, it's just meant to provide information to the programmers using the code, and to stop programmers form doing simple mistakes. Getting a compile error when making a mistake is very helpful and imo preferable.

I find it super useful, and especially when working with large code bases and larger teams, it gets more and more important, though I can understand that some thinks it looks like bloat at times... aseptically when some get function have const and none const versions...

If you want, you can easily go around const with a simple cast... that's how secure it is...

void SecureFunction(const int& sectureConstInt_ReadOnlyPLZ)
{
((int)sectureConstInt_ReadOnlyPLZ) = 3; //write only right?...
}
5sw
Sven
31 posts
Constpiracy
owensd
You cannot make that claim at all. What if function() created a thread that delayed execution of the code that used the pointer to the struct? What's the value of s.x when the code in the thread executes?


Who knows. const doesn't add any guarantees to that. And I never claimed anything about this. const doesn't mean "nobody can change this value", it means "I won't change this value". Big difference.

owensd
The only way to guarantee that s.x = 123 in your example is to pass by value so that the struct is actually copied.


No, the const pointer does guarantee that. After function() returned s.x has not changed, if function played by the rules and honored the constness of the pointer. The compiler can assume this and work with that knowledge. The only way that value could change is if function() casts away the const from that pointer and changes the value anyways. But according to the C standard this is undefined behavior. Undefined behavior means anything can happen, including that the actual value in memory is actually changed but the rest of the code continues as if it wasn't (because the compiler assumed s.x == 123 after function() returned).

In this regard it's kinda like volatile. Change a variable that is not volatile from a second thread. The value in memory will be changed, but the first thread won't necessarily see that as the compiler is allowed to work with prior knowledge of the value.

owensd
The const keyword is really only good for defining the semantics of how your code should work; it really makes no claim about how your code does work.


Yes, it does, unless the code is crap. And it would do that even if the compiler was completely ignoring the const keyword. If you make your parameters const you promise every user of your code that you are not going to change them. If you go ahead then and do that anyways you lied.
David Owens II
69 posts
A software engineer that enjoys living in enemy territory.
Constpiracy
5sw
owensd
You cannot make that claim at all. What if function() created a thread that delayed execution of the code that used the pointer to the struct? What's the value of s.x when the code in the thread executes?


Who knows. const doesn't add any guarantees to that. And I never claimed anything about this. const doesn't mean "nobody can change this value", it means "I won't change this value". Big difference.


No, it says, the function "shouldn't" change this value. The function declaration cannot actually enforce the "I will not change this value."

Here's an illustration that the compiler cannot actually make that guarantee.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Data {
    int value;
};


void foo(const Data *d) {
    // d->value = 10;       // compiler error as it should
    
    Data *md = (Data *)d;
    md->value = 10;
}


int main(int argc, const char * argv[]) {
    
    Data d;
    d.value = 123;
    
    printf("value: %d\n", d.value);  // prints 123
    foo(&d);
    printf("value: %d\n", d.value);  // prints 10
    
    return 0;
}


5sw
owensd
The only way to guarantee that s.x = 123 in your example is to pass by value so that the struct is actually copied.


No, the const pointer does guarantee that.


You just said above that "const doesn't add any guarantees", and now you're saying that it does...

5sw
After function() returned s.x has not changed, if function played by the rules and honored the constness of the pointer.


That's a big if, and the compiler cannot guarantee it. The const keyword is helping _you_ (e.g. the one programming the code) protect yourself from inadvertently modifying data; it's not doing anything else.

5sw
...(because the compiler assumed s.x == 123 after function() returned).


Ok... you also have a big misunderstanding here. The function is taking a pointer to the struct; it knows nothing about the contents of the struct at this time. If you wanted the function to know the contents of the struct at call time, you'd have to pass the data in by value.

When the line of code in your function gets to: s->x, that is the only time the value is read. At that point in time, there is absolutely no guarantee as to what value will be.

You can argue that in this example it's clearly 123, but this is trivial code. If we set the value of s->x (or d.value from the example above) to a random number, then maybe it becomes more clear that the compiler really doesn't know what the value is because that value is determined at runtime. Thus the compiler has to generate code that follows the pointer in memory to retrieve the contents out of the struct.
5sw
Sven
31 posts
Constpiracy
owensd
5sw
owensd
You cannot make that claim at all. What if function() created a thread that delayed execution of the code that used the pointer to the struct? What's the value of s.x when the code in the thread executes?


Who knows. const doesn't add any guarantees to that. And I never claimed anything about this. const doesn't mean "nobody can change this value", it means "I won't change this value". Big difference.


No, it says, the function "shouldn't" change this value. The function declaration cannot actually enforce the "I will not change this value."

Here's an illustration that the compiler cannot actually make that guarantee.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
struct Data {
    int value;
};


void foo(const Data *d) {
    // d->value = 10;       // compiler error as it should
    
    Data *md = (Data *)d;
    md->value = 10;
}


int main(int argc, const char * argv[]) {
    
    Data d;
    d.value = 123;
    
    printf("value: %d\n", d.value);  // prints 123
    foo(&d);
    printf("value: %d\n", d.value);  // prints 10
    
    return 0;
}




Yes, you can write that. But you are not allowed to. This is undefined behavior, the compiler can do anything at all with that. If the compiler printed 123 twice from this example it would be OK. But the compiler also would be right to not generate code that stores a value through the cast pointer. Or if it generated code to format your hard drive.

And besides this code is crap. You don't go around changing things through const pointers. If you think this is OK to write this you shouldn't be a programmer.

owensd


5sw
owensd
The only way to guarantee that s.x = 123 in your example is to pass by value so that the struct is actually copied.


No, the const pointer does guarantee that.


You just said above that "const doesn't add any guarantees", and now you're saying that it does...


Two different things. Maybe another example will help you see the difference:

1
2
3
4
5
6
7
8
int value = 42;
const int *ptr = &value;

// *ptr == 42;

value = 0;

// *ptr == 0;


The value read through the pointer changed. The compiler guarantees nothing about *ptr, because it can't. This was the first case.

1
2
3
4
5
6
7
8
int value = 42;
const int *ptr = &value;

// value == 42

function(ptr);

// value == 42


This is a completely different case. Unless function() does something it is not allowed to, value cannot have changed.

owensd


5sw
After function() returned s.x has not changed, if function played by the rules and honored the constness of the pointer.


That's a big if, and the compiler cannot guarantee it. The const keyword is helping _you_ (e.g. the one programming the code) protect yourself from inadvertently modifying data; it's not doing anything else.



True, the compiler cannot guarantee that no braindead programmer changed the value through the const pointer. But the compiler is allowed to assume this and generate code accordingly.

I really recommend you read up on undefined behavior before you start making wrong claims. Take a look at this for some examples of really weird stuff compilers do and are allowed to do when facing things undefined by the C standard. I especially like Winner #2.

owensd



5sw
...(because the compiler assumed s.x == 123 after function() returned).


Ok... you also have a big misunderstanding here. The function is taking a pointer to the struct; it knows nothing about the contents of the struct at this time. If you wanted the function to know the contents of the struct at call time, you'd have to pass the data in by value.



We don't care about what function() gets from that struct. We are not talking about this. We talking about what happens after function() returns.

owensd


When the line of code in your function gets to: s->x, that is the only time the value is read. At that point in time, there is absolutely no guarantee as to what value will be.

You can argue that in this example it's clearly 123, but this is trivial code. If we set the value of s->x (or d.value from the example above) to a random number, then maybe it becomes more clear that the compiler really doesn't know what the value is because that value is determined at runtime. Thus the compiler has to generate code that follows the pointer in memory to retrieve the contents out of the struct.


Same thing. When you generate your random value it ends up in a register and is stored into memory at s.x from there. After the function call the compiler would be allowed to use the value from the register again without checking memory. (Yes that assumes that it still is in the register, which depends on the calling conventions and whatnot. But the compiler knows this, while I would have to look that up first...)
David Owens II
69 posts
A software engineer that enjoys living in enemy territory.
Constpiracy
6.7.5.1 Pointer declarators (source: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf)


3
EXAMPLE The following pair of declarations demonstrates the difference between a ‘‘variable pointer to a constant value’’ and a ‘‘constant pointer to a variable value’’.

const int *ptr_to_constant;
int *const constant_ptr;

The contents of any object pointed to by ptr_to_constant shall not be modified through that pointer, but ptr_to_constant itself may be changed to point to another object. Similarly, the contents of the int pointed to by constant_ptr may be modified, but constant_ptr itself shall always point to the same location.

The spec says that contents of ptr_to_constant shall not be modified through that pointer. The important part of this clause is the latter half. This means that I can create a different pointer and change that contents of that object. That is to spec. Do you have a different place in the spec where you think this says otherwise?

If your compiler output 123 in both before and after the function call by simply using the value from before and not retrieving it again, it would be a bug. Again, if you have a pointer to the spec there const pointer parameters are treated differently, let me know.

Your argument about the being "crap" is completely irrelevant; it's allowable by the language and thus must be handled properly by compilers. Failure to do so is a compiler bug.

Your #2 winner, regarding the "realloc" isn't that interesting either:


7.20.3.4 The realloc function

#2 The realloc function deallocates the old object pointed to by ptr and returns a pointer to a new object that has the size specified by size. The contents of the new object shall be the same as that of the old object prior to deallocation, up to the lesser of the new and old sizes. Any bytes in the new object beyond the size of the old object have indeterminate values.

It explicitly tells you that the passed in pointer is deallocated. It is no longer appropriate to use that pointer anymore. Doing so is most definitely undefined so it shouldn't be surprising that it result in strange and inconsistent behavior.
5sw
Sven
31 posts
Constpiracy
Edited by Sven on
owensd
6.7.5.1 Pointer declarators (source: http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf)


3
EXAMPLE The following pair of declarations demonstrates the difference between a ‘‘variable pointer to a constant value’’ and a ‘‘constant pointer to a variable value’’.

const int *ptr_to_constant;
int *const constant_ptr;

The contents of any object pointed to by ptr_to_constant shall not be modified through that pointer, but ptr_to_constant itself may be changed to point to another object. Similarly, the contents of the int pointed to by constant_ptr may be modified, but constant_ptr itself shall always point to the same location.

The spec says that contents of ptr_to_constant shall not be modified through that pointer. The important part of this clause is the latter half. This means that I can create a different pointer and change that contents of that object. That is to spec. Do you have a different place in the spec where you think this says otherwise?


Yes, you can create a different pointer to change that value. But you are not allowed to make that different pointer by casting the const away. This counts as modifying the value through the const pointer.

Lets see what the standard says to this:

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. If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.

with an footnote saying

This applies to those objects that behave as if they were defined with qualified types, even if they are never actually defined as objects in the program


owensd

If your compiler output 123 in both before and after the function call by simply using the value from before and not retrieving it again, it would be a bug. Again, if you have a pointer to the spec there const pointer parameters are treated differently, let me know.

Your argument about the being "crap" is completely irrelevant; it's allowable by the language and thus must be handled properly by compilers. Failure to do so is a compiler bug.


No it's not. You can cast the const away, but you are still not allowed to change the value through that cast pointer.

And besides legalities or not, it is just not done. Even if const was nothing more than a comment. We wouldn't have to discuss about optimizing opportunities then, but that code still would not be worth the paper it's written on.
Mārtiņš Možeiko
2559 posts / 2 projects
Constpiracy
Edited by Mārtiņš Možeiko on
5sw
Yes, you can write that. But you are not allowed to. This is undefined behavior, the compiler can do anything at all with that.


If I remember correctly last time I looked up in C standard, that is NOT undefined behaviour. You are allowed to cast off const-ness and write to casted pointer as long as value passed in is not const-only (like string literal). In your example it is not, so it is NOT undefined behaviour.

Basically writing to memory region that is placed in read-only section of memory is undefined behaviour. Not the casting process.
David Owens II
69 posts
A software engineer that enjoys living in enemy territory.
Constpiracy
5sw

Yes, you can create a different pointer to change that value. But you are not allowed to make that different pointer by casting the const away. This counts as modifying the value through the const pointer.

Lets see what the standard says to this:

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. If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.


This applies to the **OBJECT** that is defined as const (the object is the data storage, not the address of the data storage). This does **NOT** apply to the pointer that points to a non-const object.

Had your example been:

1
2
3
4
5
6
7
8
S s;
s.x = 123;

const S cs = s;

foo(&cs);

printf("cs.x = %d\n", cs.x);


THEN, it would be undefined behavior according to the rules laid out above. However, that's not the scenario you are describing. The scenario you have described is a const pointer to a non-const object.
5sw
Sven
31 posts
Constpiracy
I always interpreted the standard in a way that casting the const away and writing through that pointer was undefined behavior. Maybe I am wrong about that. I still believe it should be illegal though, and I don't write code that casts const away (except I have to interface with old code that doesn't mark its parameters as const and I know it won't change the values).

Ok, so the compiler can't use const for optimization. But it still is something the programmer needs to know. Will a function change values through pointers it takes or does it not? So what are you guys who are strict against using const doing for that? Comments on the functions? Do you just not care if a function changes things and wait until things break? Do you look at the function implementation before you decide how to call it?

Someone said here const made the code too rigid. How so? Ok, if you want to change from const to non-const you will have to go up the call-graph and make more changes. But that's just a little bit of busy work. Then comes the real work of finding all the places that depend on the called function not changing the values and rewriting it so they can deal with that change.
Andrew Bromage
183 posts / 1 project
Research engineer, resident maths nerd (Erdős number 3).
Constpiracy
5sw
I always interpreted the standard in a way that casting the const away and writing through that pointer was undefined behavior. Maybe I am wrong about that.

If you're using C++-style casts, the only way to cast the const away is to use const_cast.

Whatever you think about the semantics of casting, there needs to be some way to do it because there needs to be a way to talk to APIs (typically older C APIs) which are not const-correct. One of the ones that I had a lot of trouble with was the RenderMan Interface, which is full of calls like this:

1
2
3
4
5
6
extern RtToken RiDeclare(char *name, char *declaration);

extern RtVoid RiSubdivisionMesh(RtToken mask, RtInt nf, RtInt nverts[],
                          RtInt verts[],
                          RtInt ntags, RtToken tags[], RtInt numargs[],
                          RtInt intargs[], RtFloat floatargs[], ...);


Leave aside for the moment the fact that "void" isn't good enough for Pixar and they have to make a macro for it. That's a lot of pointer-like arguments which the API calls don't modify, but exactly none of them are declared const. Using this in with what I believed (and still kind-of believe) to be "good" C++ requires a lot of const casting.

So much const casting.
Dale Kim
22 posts
Constpiracy
Edited by Dale Kim on Reason: Edited a line to clarify the point that I have almost no code that is written just once in its final form.
5sw
Someone said here const made the code too rigid. How so? Ok, if you want to change from const to non-const you will have to go up the call-graph and make more changes. But that's just a little bit of busy work. Then comes the real work of finding all the places that depend on the called function not changing the values and rewriting it so they can deal with that change.


Const does make the code too rigid. Const makes you go through way too many hoops to do things that are trivial without const. I can't think of a single time where const has saved me time; it has only ever cost me time. Also, your proclamation that changing things from const to non const is "just a little bit of busy work" is just shocking to me. I've had to do this before... I've had to change hundreds of instances in dozens of files just because of how infectious const is. This is not a little bit of busy work; it's untenable overhead. This is the kind of thing that makes programming a complete chore.

The biggest thing about const for me is that it ossifies the code prematurely or it simply causes busywork that's not necessary in any way. Say that I'm writing code that is heavily encapsulated and I want to make things "const correct":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Foo
{
public:
    Foo()
    : m_i(0)
    {
    }

    Foo(int i)
    : m_i(i)
    {
    }

    int GetInt() const
    {
        return m_i;
    }

private:
    int m_i;
};


We, of course, know that you can promote from non const to const trivially. So code like this is no problem:

1
2
3
4
Foo f(54321);
const Foo *p = &f;

printf("%d\n", p->GetInt());


No problem, right? Let's start adding stuff to our code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Foo
{
public:
    Foo()
        : m_i(0)
    {
    }

    Foo(int i)
        : m_i(i)
    {
    }

    int GetInt() const
    {
        return m_i;
    }

private:
    int m_i;
};

class Entity
{
public:
    Entity()
        : m_name()
        , m_health(0)
        , m_foo(NULL)
    {
    }

    void InitEntity(const char *name, int health, const Foo *foo)
    {
        snprintf(m_name, sizeof(m_name), "%s", name);
        m_health = health;
        m_foo = foo;
    }

    void UpdateAndPrint()
    {
        --m_health;

        if (m_foo)
	{
            printf("%s has %d health and Foo %p -> %d\n", m_name, m_health, m_foo, m_foo->GetInt());
	}
    }

private:
    char m_name[16];
    int m_health;
    const Foo *m_foo;
};


Still not too objectionable here, we're basically just inspecting values of Foo from Entity. This code, taken for just what it is in the current state doesn't seem very bad. But my experience tells me this is bad. Why? The code as it is now is just a snapshot of some process of evolution toward a final state. I can easily imagine a situation in the future where I might want to actually change the contents of Foo from Entity.

In the current state, this isn't possible for me to do unless I remove the const qualifiers and add in some non-const functions on Foo to let me change the values. You might say, "But that's not a lot of work! Just change it in the few instances you have." Yes, in this instance I have a few number of places to change. But I've worked in code bases before where literally just going from const to non-const might force me to go through dozens of files since the call chain might be so long/complicated.

Going back to the "sample" code, if I did in fact change the code to remove the const qualifiers so I can modify Foo from Entity (and also added a setter for m_i) then what have I accomplished? I just typed a bunch of code for utterly no reason and then removed it later because it was imposing a restriction on me that's absolutely not helpful.

I really don't understand what the big deal is with people wanting to know if values won't change. I used to care a lot about "know when values won't change, therefore use const everywhere", "the compiler can help me catch bugs!", but through the last 3-4 years of programming a game, I've come to the conclusion that in the vast majority of cases this is a complete waste of time. Simply put, I don't care anymore if values can be changed or not. The entire point of my program is to change values.

That isn't to say that it isn't useful to know when something will change or not. What I am saying though, is that annotating const wherever possible is a bad move because it limits the flexibility of your code and for future modification. I haven't come up with a good analogy, but the best I've come up with so far is being a construction contractor and you're about to set up to start building. You need to set up some scaffolding so you can build up your building. Const to me is setting up the scaffolding, but instead of easy to set up/tear down pipe scaffolding, it's steel I-beams you weld in place.

The analogy isn't quite right, but my point is that const forces me to make bets on code mutability that I can't guarantee. In games, especially, it's pretty much guaranteed that there's no code anywhere that I will write just once and never touch again.

It's hard for me to come up with a really concrete example that I can illustrate in a single forum post. Const has massive costs that I can only describe through the process of writing code and evolving it. In fact, the keyword itself and what I do to the code are just complete opposites. Why should I use const at all if my code and data are always subject to change?

This is why I think using const is mostly a bad idea. Unless you live in an incredibly stable API boundary, the usage of const propagates incredible mental load and busy work in the process of evolving code. Const and evolving, they're completely opposite. It's pointless to try to mix them.
Dale Kim
22 posts
Constpiracy
I knew Casey said it somewhere, and I found where he said it on a Jeff and Casey show episode: http://mollyrocket.com/jacs/jacs_0004_0026.html [32:54]

For a very long time, I was doing things that I was told to do because "it's good programming". Not once did I ever spend the time to actually see if it was true. Since I've graduated from college, it started to grind on me that a lot of the typing I was doing seemed completely pointless. It wasn't until I heard Casey say it in this podcast when I realized... why aren't I actually measuring this? If it doesn't help me, I shouldn't be doing this.

Const was one of those things for me. It's not completely worthless, but using it in the way I was using it was definitely way more harmful and time consuming than not using it at all. Since I've dropped using const, my programming life has become pretty much all better, and not just in small ways.
4 posts
Constpiracy
DaleKim

[...]
Going back to the "sample" code, if I did in fact change the code to remove the const qualifiers so I can modify Foo from Entity (and also added a setter for m_i) then what have I accomplished? I just typed a bunch of code for utterly no reason and then removed it later because it was imposing a restriction on me that's absolutely not helpful.
[...]


I think this may be where the problem lies, const is a quite strong requirement, and putting it everywhere with no reason, just because right now, you can, will have no positive impact and you will probably end up removing it later and waste your time.

However, I think it can be useful if the surrounding code really relies on something not changing (for instance if you're working with persistent data structures), but it must be a conscious choice, you're making a promise about the future of your interface. It's a bit like "noexcept", you have to think if it's a promise which can actually help the calling code, and is worth upholding, or a detail of your current implementation which can change at any time.

For some data structures, going from mutable to immutable is not a trivial or even correct change, the structure may be used as a key in a hash table but too big to be copied (or not just big, but of variable size). Like a variable-precision integer, you want to share its memory but still have it behave like a value, you probably don't want someone else to change the internal value of the integer behind your back. (In you example, Foo has just an int, if Foo is a class representing a integer value which for some reason must be an object, not having setters may be very important, if Foo is a class holding an integer property of something, let's say some player speed, the const was indeed a bad idea from the start).
Chris
21 posts
Constpiracy
Just because I didn't see it yet on the thread. Here is an example of const helping the compiler generate better code:

http://herbsutter.com/2008/01/01/...ate-for-the-most-important-const/
Jari Komppa
41 posts / 1 project
Programmer, designer, writer, a lot of other things; http://iki.fi/sol
Constpiracy
ChrisG0x20
Just because I didn't see it yet on the thread. Here is an example of const helping the compiler generate better code:

http://herbsutter.com/2008/01/01/...ate-for-the-most-important-const/


I wouldn't call that "helping ocmpiler generate better code" but rather "obscure language feature that lets you do something weird that compiles" =)

Reading this thread has been really surprising, I would have expected "const" to help the compiler do better work at collapsing constants.

Assuming const DOES help, I think everything should be const by default and only mutable via a keyword. I've been in situations where members of the team whine about const completeness, and after writing "const" to a bunch of places it starts to feel like more things are const than not.. This naturally differs from one codebase to the next.