Const trouble

J

Johannes Bauer

Hello group,

I've run into some *very* nasty trouble which I could (after hours of
work) trace to a problem in my use of the const keyword (which
appearently has not been appropriate). It is very difficult to trace, as
it only appears when the program is compiled with g++ and -O3 (-O2 and
below work fine).

So I'm guessing it's some kind of aliasing issue.

One of my classes provides an operator*=, which takes a const Fred& as a
parameter. As soon as I remove the const, it also works with -O3.

Now since it is very difficult to trace remotely, I have some general
questions about usage of const:

1. Is it always safe to do a c-style cast in which const is *added*?
I.e. I have a method which can access a foo* r/w - can it also return
(const foo*)x?

2. Is there a difference between const_cast and a c-style cast which
modifies only the const keyword?

3. In a class like this:

class foo {
private:
int *x;
public:
void blah() const {
x[9] = 123;
}
};

Is it safe to declare blah() const?

Kind regards,
Johannes
 
A

Alf P. Steinbach

* Johannes Bauer:
Hello group,

I've run into some *very* nasty trouble which I could (after hours of
work) trace to a problem in my use of the const keyword (which
appearently has not been appropriate). It is very difficult to trace, as
it only appears when the program is compiled with g++ and -O3 (-O2 and
below work fine).

So I'm guessing it's some kind of aliasing issue.

One of my classes provides an operator*=, which takes a const Fred& as a
parameter. As soon as I remove the const, it also works with -O3.

Reproduce in small program, post complete code.

Now since it is very difficult to trace remotely, I have some general
questions about usage of const:

1. Is it always safe to do a c-style cast in which const is *added*?

No, it's not always safe to add const.

See the FAQ.

And you should avoid C style casts.

I.e. I have a method which can access a foo* r/w - can it also return
(const foo*)x?

Sorry, that's a meaningless question: the two things are not related.

2. Is there a difference between const_cast and a c-style cast which
modifies only the const keyword?

Yes.

You have no guarantee what the C style cast does.

And perhaps that's your problem, that you have introduced as reinterpret_cast
where you thought you were just adding const. Wouldn't surprise me. Remove all C
casts in your code.

3. In a class like this:

class foo {
private:
int *x;
public:
void blah() const {
x[9] = 123;
}
};

Is it safe to declare blah() const?

That depends on whether 'x' points to per-instance data or not.

It's unclear since you have chosen to express the code at the lowest possible
level of abstraction (check out e.g. std::vector in your nearest textbook).

That in turn makes it impossible for the compiler to tell you whether you're
doing something invalid or not: by using raw pointers to raw arrays you have
told the compiler to shut up, as if you know what you're doing (which you admit
here you don't, so it's silly to tell the compiler that you do), and that's also
the case with C casts, which you should remove first of all, not a single left.


Cheers & hth.,

- Alf
 
J

Johannes Bauer

Alf said:
* Johannes Bauer:

Reproduce in small program, post complete code.

Since this is not possible I have chosen to just ask my questions in
order to find the problem myself. The code is around 20 kLOC total
containing at least 6 libraries which are dynamically loaded at runtime.

If I *could* reproduce the problem chances are I could also fix it.
No, it's not always safe to add const.

See the FAQ.

I've read the FAQ section about const correctness (actually: in advance
to posting here) - I did not find this in there, though. I did not find
"const" methods in there either.
And you should avoid C style casts.

What is the difference between a reinterpret_cast and a C-style cast?
Sorry, that's a meaningless question: the two things are not related.
Sigh.


Yes.

You have no guarantee what the C style cast does.

The C style cast yields UB?
And perhaps that's your problem, that you have introduced as
reinterpret_cast where you thought you were just adding const. Wouldn't
surprise me. Remove all C casts in your code.

There were indeed a few instances where that was the case. I removed
them, the problem stayed.
3. In a class like this:

class foo {
private:
int *x;
public:
void blah() const {
x[9] = 123;
}
};

Is it safe to declare blah() const?

That depends on whether 'x' points to per-instance data or not.

Can you elaborate? Suppose there are two different scenarios:

class foo1 {
private:
int *x;
public:
foo() {
x = new int[128];
}
void blah() const {
x[9] = 123;
}
};

class foo2 {
private:
int *x;
public:
foo() {
x = singelton::get();
}
void blah() const {
x[9] = 123;
}
};

Never mind the lost memory in foo1 and suppose the static singleton::get
returns a pointer to global memory which will never change.
It's unclear since you have chosen to express the code at the lowest
possible level of abstraction

Yes, because that is where it becomes difficult. It's exactly constructs
as above that I want to know about, hence my question. This was
deliberately done so.

Regards,
Johannes
 
J

Johannes Bauer

Paavo said:
Yes, the c-style cast can be screwed up much more easily! And the
results are ill-formed or unspecified if the c-style cast does not
correspond to some certain sequences of standard casts. Otherwise, it
should be the same though.

Can you elaborate? What do you mean by screwed up more easily? Can you
provide an example? Can you point me to the part of the standard where
this is discussed?
3. In a class like this:

class foo {
private:
int *x;
public:
void blah() const {
x[9] = 123;
}
};

Is it safe to declare blah() const?

What you mean by 'safe'? This is valid C++, so I assume it should be
'safe'. Post some real code exhibiting he problem!

By safe in this case I mean: Can the "const" keyword of the blah()
method introduce trouble when a compiler is doing certain kinds of
optimization? Appearently this depends on where x is poining to, so I
clearified my question in response to Alf.

Regards,
Johannes
 
A

Alf P. Steinbach

* Johannes Bauer:
Since this is not possible
I have chosen to just ask my questions in
order to find the problem myself. The code is around 20 kLOC total
containing at least 6 libraries which are dynamically loaded at runtime.

If I *could* reproduce the problem chances are I could also fix it.


I've read the FAQ section about const correctness (actually: in advance
to posting here) - I did not find this in there, though. I did not find
"const" methods in there either.

E.g. look at
http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.17

What is the difference between a reinterpret_cast and a C-style cast?

A C style cast can perform various kinds casts, a reinterpret_cast only performs
a reinterpret_cast.

Yes.



The C style cast yields UB?

Casting often yields UB.

There were indeed a few instances where that was the case. I removed
them, the problem stayed.

How can you be sure you removed them when you're using C style casts.

You can't.

Remove the C style casts.

3. In a class like this:

class foo {
private:
int *x;
public:
void blah() const {
x[9] = 123;
}
};

Is it safe to declare blah() const?
That depends on whether 'x' points to per-instance data or not.

Can you elaborate? Suppose there are two different scenarios:

class foo1 {
private:
int *x;
public:
foo() {
x = new int[128];
}
void blah() const {
x[9] = 123;
}
};

class foo2 {
private:
int *x;
public:
foo() {
x = singelton::get();
}
void blah() const {
x[9] = 123;
}
};

Never mind the lost memory in foo1 and suppose the static singleton::get
returns a pointer to global memory which will never change.

In that case the constness of foo2::blah is technically correct.

The correctness of the constness of foo1::blah depends on whether x represents
per-instance data or not.

If it does, that is, if the x in foo1 can be replaced with a std::vector<int>,
then the constness of foo1::blah is incorrect, as the compiler will *tell* you
Yes, because that is where it becomes difficult.

It only becomes "difficult" because you're actively removing every clue about
what you're actually trying to do. Unless you want the "difficulty" that's just
stupid. Stop removing information about what you're trying to express, and
voilà, no more "difficulties" -- or at least, greatly reduced frequency.

It's exactly constructs
as above that I want to know about, hence my question. This was
deliberately done so.

Huh.


Cheers & hth.,

- Alf
 
J

Juha Nieminen

Johannes said:
I've run into some *very* nasty trouble which I could (after hours of
work) trace to a problem in my use of the const keyword (which
appearently has not been appropriate). It is very difficult to trace, as
it only appears when the program is compiled with g++ and -O3 (-O2 and
below work fine).

It would make answering easier if you had described in more detail the
problem. "Some very nasty trouble" is not enough to know what's going on
with your program.

If the problem is related to 'const', then one could assume that you
are getting some kind of compiler (or maybe linker) error. However, your
post (rather remotely) implies that what you are getting is a runtime
problem. But what kind of problem? Crashing? Wrong results? What?

Since 'const' is a purely compile-time feature which has basically no
effect on the produced machine code, I would guess that either you have
some other bug in your program (quite probable), probably related to
writing out of boundaries, and it's just showing weird symptoms, as they
tend to do, or maybe it's a compiler bug (unlikely).
 
B

Bart van Ingen Schenau

Johannes said:
Can you elaborate? What do you mean by screwed up more easily? Can you
provide an example? Can you point me to the part of the standard where
this is discussed?

C-style casts can be screwed up more easily, because it is a kind of
'super cast' that gives the compiler much less room to diagnose possible
problems than with any of the C++ casts (reinterpret_cast included).

As it is a "shut up and do as I tell you" situation, even a simple typo
may go undiagnosed when using a C-style cast, whereas using the
appropriate C++ cast the compiler would have complained loudly.
3. In a class like this:

class foo {
private:
int *x;
public:
void blah() const {
x[9] = 123;
}
};

Is it safe to declare blah() const?

What you mean by 'safe'? This is valid C++, so I assume it should be
'safe'. Post some real code exhibiting he problem!

By safe in this case I mean: Can the "const" keyword of the blah()
method introduce trouble when a compiler is doing certain kinds of
optimization? Appearently this depends on where x is poining to, so I
clearified my question in response to Alf.

Adding const to a member function only affects members that are
physically part of the class (like x in the example) in what you can do
with them. Members that are not physically part of the class but do
logically belong to the class are unaffected (like the memory that x
refers to).

Adding const to a member without thinking about this distinction between
physical members and logical members can wreak havoc with the
expectations that other classes may have. For example, you could have
the expectation that calling foo:blah() does not change the state of the
foo object, but that expectation would be broken.
But it does not affect in any significant way the optimisations that can
be applied.
Regards,
Johannes
Bart v Ingen Schenau
 
J

Juha Nieminen

Hendrik said:
Juha said:
[...]
Since 'const' is a purely compile-time feature which has basically no
effect on the produced machine code, I would guess that either you have
some other bug in your program (quite probable), probably related to
writing out of boundaries, and it's just showing weird symptoms, as they
tend to do, or maybe it's a compiler bug (unlikely).

Might not 'const' affect the optimizer?

That might certainly be the case. If the problem is caused by
optimization (but the code itself is otherwise completely correct), then
it sounds more like a compiler bug.
 
J

Johannes Bauer

Alf said:

Ehrm, are you sure that is the right thing? It says "[18.17] Why am I
getting an error converting a Foo** → const Foo**?", the class Foo
mentioned there does not have any method declared const. Am I missing
something here?
How can you be sure you removed them when you're using C style casts.

You can't.

Remove the C style casts.

Hey, that's what I wrote: "I remove them [the C-style casts]".

Ahh! Now I get it. You thought I meant "instances" as in "class
instances", but I actually meant "there were cases were I used C-style
casts, I removed them".

English is not my mother language, sorry about the confusion.
In that case the constness of foo2::blah is technically correct.

The correctness of the constness of foo1::blah depends on whether x
represents per-instance data or not.

If it does, that is, if the x in foo1 can be replaced with a
std::vector<int>, then the constness of foo1::blah is incorrect, as the
compiler will *tell* you if do change over to std::vector<int>.

Thanks for the explanation! I have one more case that I would like to
know about: What if we have a class member int*, which refers to
per-instance data. Then suppose a method returns this raw pointer:

int* foo::getptr1() const {
return x;
}

const int* foo::getptr2() const {
return x;
}

Is getptr1 incorrect vs. the correct getptr2?
It only becomes "difficult" because you're actively removing every clue
about what you're actually trying to do. Unless you want the
"difficulty" that's just stupid. Stop removing information about what
you're trying to express, and voilà, no more "difficulties" -- or at
least, greatly reduced frequency.

No, I really do not want to hide any information. I merely am unsure
about exactly those kinds of situations that I described above and
needed clearification (which I got from you, thanks again).

Kind regards,
Johannes
 
J

Johannes Bauer

Bart said:
C-style casts can be screwed up more easily, because it is a kind of
'super cast' that gives the compiler much less room to diagnose possible
problems than with any of the C++ casts (reinterpret_cast included).

Can you provide an example where a C-style cast would just work, but a
reinterpret_cast would fail with a compiler warning/error? I'm curious
because I really am unsure about what the difference between the
reinterpret_cast and C-style cast is.
Adding const to a member function only affects members that are
physically part of the class (like x in the example) in what you can do
with them. Members that are not physically part of the class but do
logically belong to the class are unaffected (like the memory that x
refers to).

Adding const to a member without thinking about this distinction between
physical members and logical members can wreak havoc with the
expectations that other classes may have. For example, you could have
the expectation that calling foo:blah() does not change the state of the
foo object, but that expectation would be broken.

Thank you for that clearification, this is what I thought would be the
case (because actually setting x[9] in my above example *does* change
the state of the class, if class management data is stored in x).
But it does not affect in any significant way the optimisations that can
be applied.

I thought the const() method keyword was one significant part of
optimization? When the compiler knows the call to the method does not
change the state and there are two (or more) sequential calls to that
method, it might replace those calls with just a single call and reuse
the result over and over.

Kind regards,
Johannes
 
J

Johannes Bauer

Juha said:
If the problem is related to 'const', then one could assume that you
are getting some kind of compiler (or maybe linker) error. However, your
post (rather remotely) implies that what you are getting is a runtime
problem. But what kind of problem? Crashing? Wrong results? What?

Yes since I explained above the program is far too huge to boil the
problem down. And I can reproduce it only under very certain conditions
(e.g. if I add some debug std::cerrs there, the error disappears). One
of the things that made the problem go away was removing the "const"
keyword of a data chunk class I get (which, btw, uses
std::vector<unsigned char> lying beneath).

So actually this is just the introduction to the question, the problem I
have to find myself. If you're interested anyways: GCC-4.1 runs ok with
-O0, -O1, -O2 (no valgrind issues), -O3 segfaults. GCC-4.3 the same. ICC
never crashes (no valgrind issues either), no matter what optimization
level. This is on x86_64, I actually could try x86_32 (haven't so far).
Since 'const' is a purely compile-time feature which has basically no
effect on the produced machine code, I would guess that either you have
some other bug in your program (quite probable), probably related to
writing out of boundaries, and it's just showing weird symptoms, as they
tend to do, or maybe it's a compiler bug (unlikely).

Well, I am not so naive to believe in a compiler bug - although I must
admit that I compared the -O2 and -O3 assembly code and am puzzled about
those vector operations the CPU provides. Back in the days I was used to
the plain old fxyz commands, those seemed easier...

Kind regards,
Johannes
 
K

Krice

And I can reproduce it only under very certain conditions
(e.g. if I add some debug std::cerrs there, the error disappears).
One of the things that made the problem go away was removing the
"const" keyword of a data chunk class

It's possible that the memory layout changes when you add
something like const, but it's not the cause of a memory
corruption bug.
With raw arrays never assume anything. Always check
for illegal index and make sure the array is constructed
before any possible use.
 
A

Alf P. Steinbach

* Johannes Bauer:
Alf said:

Ehrm, are you sure that is the right thing? It says "[18.17] Why am I
getting an error converting a Foo** → const Foo**?", the class Foo
mentioned there does not have any method declared const. Am I missing
something here?

The context?

It was about whether it could be dangerous to add 'const' via a cast.

The above FAQ item illustrates one such situation (although the FAQ is directly
about why the compiler balks when there is no explicit cast: it balks because
it's generally unsafe to add that 'const').


[snip]
Thanks for the explanation! I have one more case that I would like to
know about: What if we have a class member int*, which refers to
per-instance data. Then suppose a method returns this raw pointer:

int* foo::getptr1() const {
return x;
}

const int* foo::getptr2() const {
return x;
}

Is getptr1 incorrect vs. the correct getptr2?

If what the 'int*' points is indeed per-instance data then you should be able to
replace the 'int*' member with just an 'int' member or a 'std::vector<int>'
member. And with either replacement the compiler will balk at getptr1()
returning a pointer or reference that can be used to modify. Not all 'const'
issues are this simple, but many are: the compiler will tell you, if you just
tells it what it's all about (like, directly using 'int' or 'std::vector<int>').


Cheers & hth.,

- Alf
 
J

Jerry Coffin

[ ... ]
Can you provide an example where a C-style cast would just work, but a
reinterpret_cast would fail with a compiler warning/error? I'm curious
because I really am unsure about what the difference between the
reinterpret_cast and C-style cast is.

While a C-style cast _can_ act like a reinterpret_cast, depending on
the situation, it can also act like a static_cast:

unsigned x = 12u;
int y = (int)x;

If you try to use a reinterpret_cast instead, the compiler should
issue a diagnostic:

unsigned x = 12;
int y = reinterpret_cast<int>(x);

If I compile this with VC++ (for one example), I get:

-----------------------------
reinterpret.cpp(4) : error C2440: 'reinterpret_cast' : cannot convert
from 'unsigned int' to 'int'
Conversion is a valid standard conversion, which can be
performed implicitly or by use of static_cast, C-style cast or
function-style cast
-----------------------------

As implied by the error message, this conversion can even be done
implicitly:

int y = x;

but adding the reinterpret_cast makes it ill formed.

A C-style cast can also act like a const_cast:

int x = 10;
int const &y = x;
int &z1 = (int &)y; // cast away const.
int &z2 = const_cast<int &>(y); // cast away const
int &z3 = reinterpret_cast<int &>(y); // fails.

This time, VC++'s error message reads:

reinterpret.cpp(7) : error C2440: 'reinterpret_cast' : cannot convert
from 'const int' to 'int &'
Reason: cannot convert from 'const int *' to 'int *'
Conversion loses qualifiers

A C-style cast can also combine conversions, for example, producing
results equivalent to a static_cast followed by a const_cast:

class B {};
class D: public B {};

int main() {
D d;
B const *b = &d;

D *d2a = (D *)b;
D *d2b = const_cast<D *>(static_cast<D const *>(b));
return 0;
}

There are also conversions to inaccessible base types that a C-style
cast can do that no new-style cast can. The full list of conversions
you can do with a C-style cast is given in section 5.4 of the
standard.
 
G

Gerhard Fiedler

Krice said:
It's possible that the memory layout changes when you add something
like const, but it's not the cause of a memory corruption bug. With
raw arrays never assume anything. Always check for illegal index and
make sure the array is constructed before any possible use.

Also, the timing can change, with optimization changes, with
adding/removing const (which may affect optimizations), and with adding
debug output.

Gerhard
 
B

Bart van Ingen Schenau

Johannes said:
Can you provide an example where a C-style cast would just work, but a
reinterpret_cast would fail with a compiler warning/error? I'm curious
because I really am unsure about what the difference between the
reinterpret_cast and C-style cast is.

This will compile:
const int i;
(double*)&i;
(double)3.14f;
but this not:
const int i;
reinterpret_cast<double*>(&i);
reinterpret_cast<double>(3.14f);

If you look at clause 5.4 [expr.cast], it is stated that C-style cast
notation can perform the same conversion as
- const_cast
- static_cast
- static_cast followed by const_cast
- reinterpret_cast
- reinterpret_cast followed by const_cast

The C++ casts on the other hand are largely mutually exclusive, so you
have to know much better which conversion you actually want and how safe
that conversion is.

I thought the const() method keyword was one significant part of
optimization? When the compiler knows the call to the method does not
change the state and there are two (or more) sequential calls to that
method, it might replace those calls with just a single call and reuse
the result over and over.

That kind of thing can only be done if the compiler can prove that the
function is a 'pure' function: it has no side-effects of any kind (no
I/O, no modification of non-member objects, no access of volatile
variables, no calling other (non-pure) functions).
This kind of proof is still outside the capabilities of a C++ compiler.
Kind regards,
Johannes
Bart v Ingen Schenau
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,982
Messages
2,570,185
Members
46,736
Latest member
AdolphBig6

Latest Threads

Top