casting const away

G

gwowen

In a situation like this

void my_func(const FOO_T *);

*foo = 'b';
my_func(foo);
putchar(*foo);

Is the compiler allowed to replace the second foo->a with 'b'
i.e. allowed to assume that my_func does indeed not modify
foo?

Certainly not in general.

int x = 7;
void bar(const int*);
int main()
{
x = 7;
const int *p = &x;
bar(p); printf("%d\n",*p);
}

// Now in a separate translation unit...
extern int x;
void bar(const int* d)
{
// x can't be changed through p ... but
// x can still be changed
if(x==0) x = *d;
else x = 0;
return;
}

// What should this program print?
// Of course, in certain cases the compiler may be able to do alias
analysis and make the change, but not in general.
 
K

Kelsey Bjarnason

"copx" wrote in message Here's
some compile-ready code which illustrates the issue:

#include <stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';

foo(&a);

putchar(a);

return 0;
}


====
Result: no warnings, prints 'B';

As I wrote in the other post I think I have figured it out already.

In this example, as you say, it appears to work, but I believe the key
term there is "appears to".

You've essentially lied to the compiler; telling it (where main calls
foo) that the value will not - indeed cannot - be modified, but then you
modify it.

AIUI, the compiler is free to believe what it's told - that the parameter
is, indeed, const and thus won't be modified. In a somewhat less trivial
example, it may well decide to stuff the value ('A') into a register,
then use that in passing the value to putchar, meaning that the
modifications you made in foo() have no effect on the value displayed -
even though the value of a itself may in fact have been modified.

Something like this (pseudo assembler):

load reg1, 'A'
load reg2, reg1 ; since the value won't be modified, cache it
store [a], reg1 ; assign to a
load reg1, @a ; get address of a into reg1
call foo,reg1 ; call foo with address of a, in reg1
load reg1, reg2 ; retrieve cached value
call putchar, reg1 ; use cached - original - value for putchar


The fact you are in essence lying to the compiler about what main can
expect to happen - or, rather, not happen - to a suggests to me the
compiler is free to do whatever it thinks is right - which may differ
from run to run, optimization to optimization, compiler to compiler, etc.
 
I

Ian Collins

That circumstance hold in this case, as another posting from copx in
this thread makes clear.

I was responding to the case he posted:

void foo(const int *c)
{
*((int *)c) = 'B';
}
Certainly it is true that if a pointer to
an object that was defined as const-qualified is casted and used to
store then the resulting behavior is undefined.

Given the function above, there's no way (inside foo) of knowing whether
the original object was const-qualified or not.
The question,
however, is what happens when a pointer to a non-const-qualified
object is converted to a pointer to a const-qualified type, which is
subsequently converted back to the original type and then used to
store. The behavior in such circumstances is well-defined, not
undefined.

True. But that guarantee can't be met with a function parameter.
The reasoning about casting being lying to the compiler
is bogus; there is nothing wrong with this casting in the case
under consideration.

That wasn't how I read it.
 
T

Tim Rentsch

Ian Collins said:
I was responding to the case he posted:

void foo(const int *c)
{
*((int *)c) = 'B';
}

This function _must work_ if called with an argument of
type (int *) that is a valid (non-null) pointer (to an
object of type int). Do you agree?

Given the function above, there's no way (inside foo) of knowing
whether the original object was const-qualified or not.

That's true, but even so the function foo() must work if
called with a pointer to a modifiable int object. It
doesn't have to work if called with a pointer to an object
defined as const-qualified, but it _does_ have to work if
called with a pointer to an object defined without const.
True. But that guarantee can't be met with a function parameter.

Indeed it can, and the function in question must be compiled
so that it will.
That wasn't how I read it.

It would help if you could be a little more explicit in
saying just what interpretation you believe is actually
the case. I will start the ball rolling. The program
copx posted:

#include <stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';
foo(&a);
putchar(a);
return 0;
}

does _not_ have any undefined behavior. Do you dispute
that? If so, please cite those sections of the Standard
that support your view.
 
M

Marcin Grzegorczyk

Francis said:
[...]
For example:

int i; const int* p =&i; * (int *) p = 1;

is perfectly fine, but


const int i; const int* p =&i; * (int *) p = 1;

is undefined behaviour. The first doesn't modify a const object, the
second one does. Use "const int* restrict" instead:

int i; const int* restrict p =&i; * (int *) p = 1; // Undefined
behaviour

Could you argument your last example ?

I don't see why adding 'restrict' qualifier invokes undefined
behaviour. For me, the cast is defined and the effective rules are
respected.

The second sentence of 6.7.3.1p4 (Formal definition of restrict) says:
# 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.
(and then it goes on to list the other requirements)

In that sentence, L is any lvalue that has &L derived (directly or
indirectly) from some restrict-qualified pointer.

Thus, a modification - by any means - of an object pointed to by a
restrict-qualified pointer to a const-qualified type causes undefined
behaviour, unless that object is never accessed using an expression
derived from the said restrict-qualified pointer (within the pointer's
lifetime).

I'm aware that 6.7.3.1 is not easy to understand, but once you get
though the terse wording, at least that point becomes pretty clear.
 
F

Francis Moreau

christian.bau said:
That is _exactly_ one of the two situations that "restrict" was
invented for: To tell the compiler that anything that is accessed
through a const* restrict pointer is not going to be modified by any
means at all, as long as the const* restrict pointer variable is in
scope, to allow compiler optimisations. Otherwise, take this example:

Ok, I didn't know that usage of 'restrict'.

Actually I must admit that I tried a couple of time to read the formal
definition of 'restrict' and have never understood it: it's just a pain
to read and have always found something more interesting to do...

Sigh, need to fix this though.
 
F

Francis Moreau

Marcin Grzegorczyk said:
Francis said:
[...]
For example:

int i; const int* p =&i; * (int *) p = 1;

is perfectly fine, but


const int i; const int* p =&i; * (int *) p = 1;

is undefined behaviour. The first doesn't modify a const object, the
second one does. Use "const int* restrict" instead:

int i; const int* restrict p =&i; * (int *) p = 1; // Undefined
behaviour

Could you argument your last example ?

I don't see why adding 'restrict' qualifier invokes undefined
behaviour. For me, the cast is defined and the effective rules are
respected.

The second sentence of 6.7.3.1p4 (Formal definition of restrict) says:
# 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.
(and then it goes on to list the other requirements)

In that sentence, L is any lvalue that has &L derived (directly or
indirectly) from some restrict-qualified pointer.

Thus, a modification - by any means - of an object pointed to by a
restrict-qualified pointer to a const-qualified type causes undefined
behaviour, unless that object is never accessed using an expression
derived from the said restrict-qualified pointer (within the pointer's
lifetime).

I'm aware that 6.7.3.1 is not easy to understand, but once you get
though the terse wording, at least that point becomes pretty clear.

I've never got it unfortunately.

BTW is there any documentation about restrict more readable (I mean with
simple sentences, examples with main points...) out there ?

Thanks.
 
C

copx

"Kelsey Bjarnason" wrote in message
You've essentially lied to the compiler;

There is no such thing as "lying to the compiler" in ISO C.
telling it (where main calls
foo) that the value will not - indeed cannot - be modified, but then you
modify it.

The meaning of "const" in ISO C is not defined by the English
word "constant" but only by the standard. The wording of
the standard does not support your claim.

Really, this next group has become almost useless. A few years
ago Richard Heathfield, Dan Pop, Lawrence Kirby, and
if necessary Steve Summit himself would have all agreed on what
the standard says, and thus the question would have been solved
with final authority.

I am sure that I am right, but of course so you are. This group
no longer has the necesary amount of universally respected,
active (and aggressive) C experts to decide such questions.
That we are still arguing about this is the proof.

c.l.c. with it's strict definition of what's on topic (i.e. if it's not
in the standard it's off-topic here) used to be good for exactly
one thing: answering questions like this with final authority.

Now that this trait is gone this group might as well disband.

Really, I don't come here for endless internet debates with
random nobodys (such as yourself) whose opinion is about
as relevant as my own.

Next time I will just try to figure out what the standard
says myself and don't even bother with this group. Such
endless arguing is just a waste of time.
 
B

Ben Bacarisse

christian.bau said:
1. If an object is accessed using an lvalue based on a "const
restrict*" pointer, and the same object is modified in any way,
behaviour is undefined.

2. If an object is accessed or modified using an lvalue based on a
"restrict*" pointer, and the same object is modified using an lvalue
that is _not_ based on the same "restrict*" pointer, then behaviour is
undefined.

3. If an object is modified using an lvalue based on a "restrict*"
pointer, and the same object is accessed or modified using an lvalue
that is _not_ based on the same "restrict*" pointer, then behaviour is
undefined.

To explain "based on" a simple example:

int i;
int restrict* p = &i;
int* q = p;
int* r = &i;

The lvalue *q is "based on p" because it has been calculated using the
address of p only (but not the value that p points to), but the
lvalues i or *r are not "based on p". Basically the compiler is
allowed to assume that reading or writing data through the pointer p
and anything derived from it does _not_ interfere with reading or
writing data through lvalues that are _not_ derived from p.

You've used "restrict*" in several places (hence the full quote) where
you should really have written "*restrict". In some cases, I don't
think you mean it to be C code, but even then it's at the least rather
confusing -- more so since you do write the same think when it clearly
is C code.
 
T

Tim Rentsch

Kelsey Bjarnason said:
"copx" wrote in message Here's
some compile-ready code which illustrates the issue:

#include <stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';

foo(&a);

putchar(a);

return 0;
}


====
Result: no warnings, prints 'B';

As I wrote in the other post I think I have figured it out already.

In this example, as you say, it appears to work, but I believe the key
term there is "appears to".

You've essentially lied to the compiler; telling it (where main calls
foo) that the value will not - indeed cannot - be modified, but then you
modify it.

AIUI, the compiler is free to believe what it's told - that the parameter
is, indeed, const and thus won't be modified. [snip elaboration]

This understanding is mistaken. All operations in the above program
are permitted and well-defined by the ISO standard (not counting the
minor exception that there is no final newline sent to stdout). I
recommend reading the relevant portions of the Standard, especially
those describing pointer conversions and rules for accessing in the
presence of const-qualified definitions, to verify this.
 
K

Kelsey Bjarnason

[snips]

AIUI, the compiler is free to believe what it's told - that the
parameter is, indeed, const and thus won't be modified. [snip
elaboration]
This understanding is mistaken.

On the one hand, you may well be right. On the other, in 20-odd years of
C coding, I don't think I've ever found it necessary to lie to the
compiler this way. On the third hand, I'd tend to suggest any code which
uses such constructs needs to be re-thought, rather than diddling over
relatively obscure portions of the standard - and hoping the implementor
got the intention spot-on. :)
 
K

Kelsey Bjarnason

[snips]

The problem is not the code, the problem is that the compiler has to
work correctly no matter how stupid the code is. But now consider the
strchr function: char *strchr(const char *s, int c); Consider this
example:

void f (char* p) {
if (strchr (p, 'x') != NULL) *strchr (p, 'x') = '\0';
}

Blech; people actually write code like that? "*strchr(...) = 0"?
p is cast to a const char*. I lied to the compiler.

It is? p, at this point, is known to be modifiable (or, more to the
point, to point to a modifiable memory region); else it would be const.
Thus any subsequent fiddling of said memory (barring out of bounds issues
and the like) are perfectly valid.

The compiler knows
that the result of strchr will point to the same array if it is not
NULL. So I am effectively changing a char derived from a const char*.


Actually, you're returning a char *, which was initially a char *.
strchr, by way of its use of a const char * parameter, simply assures
that the value will not be modified by the function.

Now, if strchr were to, in fact, modify the contents of the buffer
pointed to by its const char * parameter, then we'd have a comparable
situation - but it doesn't.

To make this a comparable case, we would have do to something such as:


char *s = "Hello";

* strchr( s, 'e' ) = 'x';

Here _we_ are lying to the compiler, by telling it that the non-const
return from strchr actually points to a modifiable buffer, when it
doesn't. strchr isn't lying here; it's simply assuring us it won't
modify the buffer. The OP's code also made that same assurance, but then
promptly violated it. If strchr violates that assurance, get a new
library or implementation, the one you're using is broken.
 
M

Michael Press

Tim Rentsch said:
[...]
That wasn't how I read it.

It would help if you could be a little more explicit in
saying just what interpretation you believe is actually
the case. I will start the ball rolling. The program
copx posted:

#include <stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';
foo(&a);
putchar(a);
return 0;
}

does _not_ have any undefined behavior. Do you dispute
that? If so, please cite those sections of the Standard
that support your view.

That code is in some kind of difficulty.
Suppose foo's caller wants and expects the value retained (*&a)?

$ cc -Wcast-qual try.c
try.c: In function 'foo':
try.c:5: warning: cast discards qualifiers from pointer target type

$ cat -n try.c
1 #include <stdio.h>
2
3 void foo(const int *c)
4 {
5 *((int *)c) = 'B';
6 }
7
8
9 int main(void)
10 {
11 int a = 'A';
12 foo(&a);
13 putchar(a);
14 return 0;
15 }
 
B

Ben Bacarisse

Michael Press said:
Tim Rentsch said:
[...]
That wasn't how I read it.

It would help if you could be a little more explicit in
saying just what interpretation you believe is actually
the case. I will start the ball rolling. The program
copx posted:

#include <stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';
foo(&a);
putchar(a);
return 0;
}

does _not_ have any undefined behavior. Do you dispute
that? If so, please cite those sections of the Standard
that support your view.

That code is in some kind of difficulty.

Can you explain what difficulty you see? Your choice of words suggests
that you agree that the code does not have any undefined behaviour.
Suppose foo's caller wants and expects the value retained (*&a)?

I don't see any problem with that.

<snip>
 
M

Michael Press

Ben Bacarisse said:
Michael Press said:
Tim Rentsch said:
[...]

That wasn't how I read it.

It would help if you could be a little more explicit in
saying just what interpretation you believe is actually
the case. I will start the ball rolling. The program
copx posted:

#include <stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';
foo(&a);
putchar(a);
return 0;
}

does _not_ have any undefined behavior. Do you dispute
that? If so, please cite those sections of the Standard
that support your view.

That code is in some kind of difficulty.

Can you explain what difficulty you see? Your choice of words suggests
that you agree that the code does not have any undefined behaviour.
Suppose foo's caller wants and expects the value retained (*&a)?

I don't see any problem with that.

A function takes an argument of type pointer to const
int, then modifies the int; and you do not see a problem?
The compact with the compiler works both ways. The
coder agrees to abide by the promises he makes; and
he expects the compiler to enforce the promises the
compiler makes: in this case to kick up a fuss when
the implementer of foo attempts to modify a const.

For another thing you excised the compiler warning.
 
J

James Kuyper

Ben Bacarisse said:
Michael Press said:
[...]

That wasn't how I read it.

It would help if you could be a little more explicit in
saying just what interpretation you believe is actually
the case. I will start the ball rolling. The program
copx posted:

#include<stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';
foo(&a);
putchar(a);
return 0;
}

does _not_ have any undefined behavior. Do you dispute
that? If so, please cite those sections of the Standard
that support your view.

That code is in some kind of difficulty.

Can you explain what difficulty you see? Your choice of words suggests
that you agree that the code does not have any undefined behaviour.
Suppose foo's caller wants and expects the value retained (*&a)?

I don't see any problem with that.

A function takes an argument of type pointer to const
int, then modifies the int; and you do not see a problem?

No, I do not; such code has well-defined behavior according to the C
standard. I would expect that anyone writing such code probably knows
what that well-defined behavior is, and would probably write such code
only if that behavior was desired. Those 'probably's cover the
possibility that such code might have been written in error, but there's
little evidence that such an error was made.

In this simple example, the 'const' on the declaration of 'c' is not
merely pointless, but actually forces the otherwise unnecessary use of a
cast to remove it. In real world code, that would be a sign of poor
design, unless there were a good reason why '*c' had to be const. The
most obvious possibility would be because that was the signature
required for a call-back function that is part of a third-party library.
However, this is clearly not real world code, it serves only as an
example for purposes of discussing the fact that the C standard allows
such code.
The compact with the compiler works both ways. The
coder agrees to abide by the promises he makes; ...

As far as the C standard is concerned, the promise made when you declare
that a function is "const int *c", is that your code will do things like
*(int*)c = 'B' only if c is known to point to an object that is not
defined as const. Since the above code makes sure that foo() is only
called on &a, and since 'a' is not defined as const, there's no
violation of that promise.
... and
he expects the compiler to enforce the promises the
compiler makes: in this case to kick up a fuss when
the implementer of foo attempts to modify a const.

The only promise the standard requires of the compiler is to "raise a
fuss" if an attempt is made to write through such a pointer without
first explicitly converting it to a pointer to non-const. With such a
cast, no diagnostic is required.

No attempt was made to modify a object defined as const. 'a' is not a
const. The type of *(int*)c is not const-qualified, either. More
importantly, in this code, 'c' never points at any object other than
'a'. Therefore, *(int*)c does not refer to any object defined as const.

....
For another thing you excised the compiler warning.

It was only labeled as a warning; it was not a mandatory diagnostic. A
compiler can choose to warn you about anything it wants. It could warn
you not to compile programs on Friday the 13th. The fact that a warning
was issued is not relevant to the question of whether or not this code
has defined behavior.
 
M

Michael Press

James Kuyper said:
Ben Bacarisse said:
[...]

That wasn't how I read it.

It would help if you could be a little more explicit in
saying just what interpretation you believe is actually
the case. I will start the ball rolling. The program
copx posted:

#include<stdio.h>

void foo(const int *c)
{
*((int *)c) = 'B';
}


int main(void)
{
int a = 'A';
foo(&a);
putchar(a);
return 0;
}

does _not_ have any undefined behavior. Do you dispute
that? If so, please cite those sections of the Standard
that support your view.

That code is in some kind of difficulty.

Can you explain what difficulty you see? Your choice of words suggests
that you agree that the code does not have any undefined behaviour.

Suppose foo's caller wants and expects the value retained (*&a)?

I don't see any problem with that.

A function takes an argument of type pointer to const
int, then modifies the int; and you do not see a problem?

No, I do not; such code has well-defined behavior according to the C
standard. I would expect that anyone writing such code probably knows
what that well-defined behavior is, and would probably write such code
only if that behavior was desired. Those 'probably's cover the
possibility that such code might have been written in error, but there's
little evidence that such an error was made.

In this simple example, the 'const' on the declaration of 'c' is not
merely pointless, but actually forces the otherwise unnecessary use of a
cast to remove it. In real world code, that would be a sign of poor
design, unless there were a good reason why '*c' had to be const. The
most obvious possibility would be because that was the signature
required for a call-back function that is part of a third-party library.
However, this is clearly not real world code, it serves only as an
example for purposes of discussing the fact that the C standard allows
such code.
The compact with the compiler works both ways. The
coder agrees to abide by the promises he makes; ...

As far as the C standard is concerned, the promise made when you declare
that a function is "const int *c", is that your code will do things like
*(int*)c = 'B' only if c is known to point to an object that is not
defined as const. Since the above code makes sure that foo() is only
called on &a, and since 'a' is not defined as const, there's no
violation of that promise.
... and
he expects the compiler to enforce the promises the
compiler makes: in this case to kick up a fuss when
the implementer of foo attempts to modify a const.

The only promise the standard requires of the compiler is to "raise a
fuss" if an attempt is made to write through such a pointer without
first explicitly converting it to a pointer to non-const. With such a
cast, no diagnostic is required.

No attempt was made to modify a object defined as const. 'a' is not a
const. The type of *(int*)c is not const-qualified, either. More
importantly, in this code, 'c' never points at any object other than
'a'. Therefore, *(int*)c does not refer to any object defined as const.

...
For another thing you excised the compiler warning.

It was only labeled as a warning; it was not a mandatory diagnostic. A
compiler can choose to warn you about anything it wants. It could warn
you not to compile programs on Friday the 13th. The fact that a warning
was issued is not relevant to the question of whether or not this code
has defined behavior.

Okay. Thanks. What you say is clear to me.
I need to think about it. The idiom seems
so common that a warning cannot be raised
even with cc -Wall -Wextra -pedantic.
 
M

Michael Press

[...]
It was only labeled as a warning; it was not a mandatory diagnostic. A
compiler can choose to warn you about anything it wants. It could warn
you not to compile programs on Friday the 13th.

But nobody does, except for an Easter egg.
Why is that? Is it because they want to
stay in the compiler writing business?
The fact that a warning
was issued is not relevant to the question of whether or not this code
has defined behavior.

It is a question---a question that two people evaded.
Why have the warning at all?
 
I

Ian Collins

It is a question---a question that two people evaded.
Why have the warning at all?

Undefined behaviour or not, the warning is still valuable. Consider a
slight variation on the original example:

#include <stdio.h>

static const int a = 'A';

void foo(const int *c)
{
*((int *)c) = 'B';
}

int main(void)
{
foo(&a);
putchar(a);
return 0;
}

Running this on many systems will cause a crash. is that well defined
behaviour?
 

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

Similar Threads


Members online

No members online now.

Forum statistics

Threads
474,083
Messages
2,570,591
Members
47,212
Latest member
RobynWiley

Latest Threads

Top