question on const

A

Alexander Bartolich

begin tinybyte:
The compiler is allowed to assume that the value it's not changed,
but sometimes it is changed, sometimes not, depends on optimization.

It is not changed by magic but by your request.
Using an explicit cast. You asked for trouble, you got trouble.
If you fix this line

ip = (int *) #

to

ip = #

you should get a diagnostic similar to

warning: assignment discards qualifiers from pointer target type

If you change the declaration to

const int *ip;

then the diagnostic will change to

warning: assignment of read-only location
This is inconsistent behavior for me, and in C programming this
should not be ALLOWED.

C gives you enough rope to hang yourself.
And then a couple of more feet, just to be sure.
Always has been, always will be.
[...] A value that cannot be changed has been changed, under
certain conditions. That means something is broken.

There are indeed languages out there that protect the code from
hostile programmers, e.g. Smalltalk, Java, C#, Python, Ruby.
If you want that, go for it.
 
C

CBFalconer

tinybyte said:
Yes, they should be detected, and result in an error.

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
int main(void)
{
int a, b, c;

a = INT_MAX;
b = rand();
(void)printf("%d\n", c = a + b);
return 0;
}

has a fairly high probability of generating undefined behavior.
Yet very few systems will complain at run-time whether or not UB
occurs, and none that I know of will complain at compile time.
 
C

CBFalconer

*** Please do NOT top-post - corrected ***
Jean-Michel Collard said:
.... snip ...

It seems to be compiler dependent and even changes with optimisations.

<jm@paris>13:42:33~$gcc -v
Reading specs from /usr/lib/gcc/i686-pc-linux-gnu/3.4.0/specs
Configured with: ../gcc/configure --prefix=/usr --enable-shared
--enable-static --enable-debug --enable-profile --verbose --enable-interpreter
--enable-haifa --enable-long-long --enable-languages=c,c++ --with-system-zlib
--enable-__cxa_atexit --enable-threads=posix
Thread model: posix
gcc version 3.4.0

<jm@paris>13:43:51~$gcc ess.c
<jm@paris>13:44:26~$./a.out
value of num is 200(200)

<jm@paris>13:44:28~$gcc -Wall -W -O2 ess.c
ess.c: In function `main':
ess.c:11: warning: control reaches end of non-void function
<jm@paris>13:44:41~$./a.out
value of num is 100(200)

With the silly top-posting fixed this may make some sense.

Something is seriously wrong with your gcc installation:

c:\c\junk>gcc -W -Wall -ansi -pedantic -O1 junk.c
junk.c:1:10: #include expects "FILENAME" or <FILENAME>
junk.c: In function `main':
junk.c:9: warning: implicit declaration of function `printf'
junk.c:10: warning: control reaches end of non-void function

After correcting the source and removing the foolish cast:

#include <stdio.h> /* corrected */
int main(void)
{
const int num = 100;
int *ip;

ip = &num; /* silly cast removed */
*ip = 200;
printf("value of num is %d(%d) \n", num, *ip);
return 0; /* line added */
}

c:\c\junk>gcc -W -Wall -ansi -pedantic -O1 junk.c
junk.c: In function `main':
junk.c:7: warning: assignment discards qualifiers from pointer
target type

Moral: CASTS ARE EVIL.
 
C

Chris Torek

The original post is missing on my server, so I am replying to
a followup.

The effect at this point is officially undefined by the C standard,
so *anything* *could* happen. What likely *does* happen is just
what is shown below:

It seems to be compiler dependent and even changes with optimisations. [without optimization]
value of num is 200(200) [with optimization]
value of num is 100(200)

This is highly expect-able. :)

First, remember that the "const" keyword in C is peculiarly named,
like so many C keywords: "struct" means "define a type"; "typedef"
means "do NOT define a new type"; "static" has nothing to do with
electricity :) ; and "const" means "read-only".

Thus:

const int num = 100;

means: "Allocate a plain old ordinary variable that could change
like any plain old ordinary variable, but please Mr Compiler, if
you are able and willing, please put it into memory that is in fact
read-only. Oh and by the way I also promise never, ever to change
this variable -- you may count on me sir, I never make misteaks!" (sic)

Now, as it happens, gcc is not able or willing to place this variable
in read-only memory (because gcc assumes there is a conventional
stack, and puts all its automatic variables on that stack, and does
not try to protect any sub-parts of it against writing). If you
compile without optimization, gcc also forgets about your promise
not to change the variable.

On the other hand, if you turn on optimization, gcc remembers your
promise. You promised the variable "num" would always be 100 --
so the printf() call can substitute in the promised value for the
first %d.

The pointer "ip", of course, points to the actual variable (which
the compiler did not place in read-only memory after all) that
was initially 100. You achieved this by using a cast to tell the
compiler: "Hey Mr Compiler, I am smarter than you: this pointer
value from &num, that has type `pointer to read-only int', really
points to a read/write int. So even though this assignment is
suspicious and dodgy and you would normally tell me I am doing
something foolish, I invoke the All-Powerful-Cast syntax to shut
you up!" And -- because gcc was unwilling or unable to put "num"
in really, truly read-only memory, this, too, is actually correct.
The variable "num" *is* an ordinary int variable when gcc is done
compiling, because -- despite your asking gcc to please put it in
read-only memory -- it went into read/write memory.

Then, when you said "*ip = 200;" you told the compiler to change
the value of the memory from its original 100 to the new 200.
The compiler no doubt generated code to do this, and since "num"
was -- despite your request -- in read/write memory, the assignment
changed it.

Thus, you broke your promise never to change "num".

You lied to the compiler. It got its revenge, by producing different
code under optimization (where it assumed you kept your promise)
than without optimization (where it did not).

Note that it requires a pointer cast to achieve this lie. Avoid
pointer casts and you will avoid many mistakes. This is part of
the reason comp.lang.c wisdom says "do not cast malloc()".
 
T

tinybyte

begin tinybyte:
It is not changed by magic but by your request.
Using an explicit cast. You asked for trouble, you got trouble.

I asked for that, I've got it, I can manage it, now I'm happy :)
C gives you enough rope to hang yourself.
And then a couple of more feet, just to be sure.
Always has been, always will be.

Nowadays too many programmers hangs themselves using C.
Knowing every inch of that rope is the only way to stay away from
hanging...
There are indeed languages out there that protect the code from
hostile programmers, e.g. Smalltalk, Java, C#, Python, Ruby.
If you want that, go for it.

I don't want that. I will keep programming in C for my whole life, because
I love it. Knowing many languages is indeed useful, even if you actually
program only in C. I don't want another language, I want the C language
the best it can be. This is the whole point of my polemic

....

Am I too much polemic? ;)

Bye
Daniele
 
T

tinybyte

The original post is missing on my server, so I am replying to
a followup. [snip]
Note that it requires a pointer cast to achieve this lie. Avoid
pointer casts and you will avoid many mistakes. This is part of
the reason comp.lang.c wisdom says "do not cast malloc()".

Ah, finally an exaustive explanation that isn't "according to the standard
there is no reason to speak about it" ... thanks a real lot Chris!
If we ever met in the future, remember that I owe you a beer! :D

Bye,
Daniele "tinybyte" Milan
 
K

Kevin Goodsell

[Cross-posted and followups set to gnu.gcc.help.]

[Note: Discussion arose from a question about strange output when a
const object is modified through a pointer with the const attribute cast
away. The OP observed that the same address appeared to contain 2
different values, depending on whether it was accessed by the const
object name or by dereferencing the pointer - typical newbie stuff
dealing with undefined behavior. Others observed that gcc doesn't seem
to do the optimization that causes the apparently strange result. OP's
code (slightly adapted) appears in my example session below.]
What gcc probably does do (and the warning message you get suggests it)
is discarding the word const from the declaration of num. Normally gcc
performs the above optimisation.

gcc doesn't even seem to warn about it with -W -Wall. It looks like
-Wcast-qual (which is one of several additional warning options I
usually enable) enables the warning. But I don't think this warning has
anything to do with the results.

gcc *should* do that optimization, if you turn optimizations on. And it
does... but I found something strange:

$ cat fun.c
#include <stdio.h>

int main(void)
{
const int num = 100;
int *ip;

ip = (int *)&num;
*ip = 200;
printf("value of num is %d(%d) \n", num, *ip);

return 0;
}


$ gcc fun.c -o fun.exe


$ ./fun
value of num is 200(200)


$ gcc -O fun.c -o fun.exe


$ ./fun
value of num is 100(200)


$ gcc -pedantic -O fun.c -o fun.exe


$ ./fun
value of num is 200(200)


Note that I compiled 3 different ways: first, with no special options,
second with basic optimizations turned on, and finally with basic
optimizations and -pedantic turned on. With optimizations (and no
-pedantic) it did the expected optimization. For some reason, turning on
-pedantic changed the results. I checked the docs and this doesn't look
right. -pedantic should only affect what diagnostics are issued, I think.

If -pedantic reduces optimization effectiveness, I think a lot of us are
going to have to change the way we use gcc.

For reference, here's my version info:

$ gcc -v
Reading specs from /usr/lib/gcc-lib/i686-pc-cygwin/3.3.1/specs
Configured with: /GCC/gcc-3.3.1-3/configure --with-gcc --with-gnu-ld
--with-gnu-as --prefix=/usr --exec-prefix=/usr --sysconfdir=/etc
--libdir=/usr/lib --libexecdir=/usr/sbin --mandir=/usr/share/man
--infodir=/usr/share/info
--enable-languages=c,ada,c++,f77,pascal,java,objc --enable-libgcj
--enable-threads=posix --with-system-zlib --enable-nls
--without-included-gettext --enable-interpreter --enable-sjlj-exceptions
--disable-version-specific-runtime-libs --enable-shared
--disable-win32-registry --enable-java-gc=boehm
--disable-hash-synchronization --verbose --target=i686-pc-cygwin
--host=i686-pc-cygwin --build=i686-pc-cygwin
Thread model: posix
gcc version 3.3.1 (cygming special)

-Kevin
 
K

Kevin Goodsell

tinybyte said:
I admit it's silly :)
Undefined behavior? then cast that pointers to void and you're done.

...
printf ("address of ip is %p\n", (void *)ip);
printf ("address of num is %p\n", (void *)&num);
...

That is the correct way to handle it, but you might want to cast to
const void * in the latter case, otherwise you may elicit a warning from
the compiler, since num is a const object.

-Kevin
 
F

Flash Gordon

Is an opinion on how C compilers should be.

Not all undefined behaviour can be detected at compile time. Some can
and some compilers will warn about such instances, but many cannot.

Does the following function invoke undefined behaviour?

int foo(int a, int b)
{
return a+b;
}

Answer, it depends on the values passed to it. Since it may be called
from a separate compilation unit the compiler can't tell.
 
F

Flash Gordon

On Thu, 08 Jan 2004 12:17:51 +0000, Thomas Stegen wrote:


No, it's not. We should go deeper than the standard. I want to learn
more than what's written in the standard, and you're helping me.
If we stop discussing when someone says "According to the standard
there is no reason to speak about this" no one will learn further,
even if possible. Now, please tell me, if this is comp.lang.c "We
speak about everything that is C related" or if "We speak only
about what is written in the C standard". Both are ok for me, but for
the latter I'll know I had to search
somewhere else for the huge deal of information we would be missing.

All you've learnt is two possible results of one invocation of undefined
behaviour. What this should teach you is to *not* invoke undefined
behaviour because *anything* could happen (I can think of other possible
results for what you did). Beyond that you don't need to know what might
happen, only how to avoid invoking undefined behaviour.
You're right.


You're right, but I would have never learned the lesson if I sticked
to the "According to the standard" dogma. We cannot rely on the
behaviour of any compiler, but we can surely speak about every and
each different behaviour, and maybe learn something more. C language
is standard + compilers, not only standard, that's what I meant.

Once you leave the bounds of what the standard defines anything could
happen. For some things outside the C standard there are other standards
that may define what happens, although these define additional libraries
and interfaces to the operating system (well, embedded compilers often
define what happens if you convert an integer to a pointer and a few
extensions) rather than defining what happens when you invoke undefined
behaviour.
I've learned NOW. Many thanks to all of you. I want to learn, but I
cannot if we stop when the standard comes in.

All you needed to do was understand when someone said you had invoked
undefined behaviour and hence anything could happen. Knowing some
possible values of anything does not help since with another compiler it
might cause your hard disk to be reformatted.
 
K

Kevin Goodsell

Thomas said:
tinybyte wrote:




No it does not. There is no right way and there is no wrong way.

I would say "There is no wrong way - all ways are right."

-Kevin
 
T

tinybyte

That is the correct way to handle it, but you might want to cast to
const void * in the latter case, otherwise you may elicit a warning from
the compiler, since num is a const object.

You're definitely right! With -Wcast-qual the warning is raised.

Bye
Daniele
 
K

Kevin Goodsell

tinybyte said:
No, it's not. We should go deeper than the standard. I want to learn
more than what's written in the standard, and you're helping me.
If we stop discussing when someone says "According to the standard there
is no reason to speak about this" no one will learn further, even if
possible. Now, please tell me, if this is comp.lang.c "We speak about
everything that is C related" or if "We speak only
about what is written in the C standard". Both are ok for me, but for the
latter I'll know I had to search
somewhere else for the huge deal of information we would be missing.

When you go "deeper than the standard" you no longer have any guarantees
about what your code will do, on this or any implementation, today or
tomorrow, etc. The standard *defines* the language. Things that it does
not define, and does not require the implementation to define, are
completely unreliable. If you intend to write code that makes use of
such things, I sincerely hope you never get a job writing programs of
any greater importance than the control systems for a toaster oven.
You're right, but I would have never learned the lesson if I sticked to
the "According to the standard" dogma. We cannot rely on the behaviour of
any compiler, but we can surely speak about every and each different
behaviour, and maybe learn something more. C language is standard +
compilers, not only standard, that's what I meant.

No, the C language is not in any way defined by compilers. And we *can*
rely on the behavior of a compliant compiler, as long as we don't invoke
undefined behavior.

Do you understand *why* we have a standard? It isn't so that you can go
and see how compiler X handles some cases of undefined behavior and code
accordingly (undefined behavior need not be consistent even on the same
implementation from one run to the next, and frequently isn't). It's so
that you can write code that will behave reliably regardless of whether
you use compiler X, Y, or Z.

-Kevin
 
K

Kevin Goodsell

tinybyte said:
The compiler is allowed to assume that the value it's not changed, but
sometimes it is changed, sometimes not, depends on optimization.
This is inconsistent behavior for me, and in C programming this should
not be ALLOWED. That is all I'm talking about.
I'm trying to say that compilers aren't perfect.

This case does not provide any evidence of that, since both compilers
performed perfectly, and produced perfectly valid results.

You seem to be having a very difficult time with the notion of undefined
behavior. Look at the Standard's definition of it:

3.18

1 undefined behavior
behavior, upon use of a nonportable or erroneous program
construct, of erroneous data, or of indeterminately valued
objects, for which this International Standard imposes no
requirements

See the "no requirements" part? It's not required to be what you expect
or what you or anyone else thinks it "should" be.
And that all those
standards everyone here care so much about are not of any help in this case.

Yes it is. It tells you what you can and cannot expect. It's the *only*
thing mandated to do that.
A value that cannot be changed has been changed, under certain conditions.
That means something is broken.

Yes. The code is broken. You can establish this fact by referring to the
standard.
Is the same as you have an application that normally gives perfect output,
but by using it in a different way it was written for, you can screw up
the whole thing. You should not be allowed to use it that way.

Why not? If the documentation says that using it that way can give
unpredictable results, it's the user's fault for doing so.
If you can, there is a bug.

Bugs are unexpected or incorrect behavior. If the behavior is documented
as being unpredictable, and this is not out of line with the system
specs, there is no bug.

This is exactly the case with the standard. It tells you what things you
can and cannot do if you want predictable, consistent results. If you go
ahead and do one of the things that gives unpredictable results anyway,
it's your own fault.

-Kevin
 
T

tinybyte

When you go "deeper than the standard" you no longer have any guarantees
about what your code will do, on this or any implementation, today or
tomorrow, etc. The standard *defines* the language. Things that it does
not define, and does not require the implementation to define, are
completely unreliable. If you intend to write code that makes use of
such things, I sincerely hope you never get a job writing programs of
any greater importance than the control systems for a toaster oven.

I want to go deeper than the standard for the only purpose of
learning, of being the best programmer I can be. I will never be using
such tricks, but now that I know them, I *understand* them better, and
eventually I am able to explain them to others exaustively, by giving them true
motivations and proofs. Not just by saying: "you should not do that because
you should not do that". This is the point.

And remember, never underestimate a toaster oven! ;)
No, the C language is not in any way defined by compilers. And we *can*
rely on the behavior of a compliant compiler, as long as we don't invoke
undefined behavior.

You're right. But I use compilers everyday, they bring my C programs to
life, and I expect from them to be as helpful as they can be.
A compiler should point out when there could be an undefined behavior.
Do you understand *why* we have a standard? It isn't so that you can go
and see how compiler X handles some cases of undefined behavior and code
accordingly (undefined behavior need not be consistent even on the same
implementation from one run to the next, and frequently isn't). It's so
that you can write code that will behave reliably regardless of whether
you use compiler X, Y, or Z.

As I said above, I don't want to write crap code with those tricks in it.
I stick to the standard when writing code, or at least I try to :)
I want to write the best code I can, and be aware of *why* that kind of code
results in undefined behavior. This is *understanding*. The other way is
just *believing*, and that could lead to mistakes too.

Bye
Daniele
 
T

tinybyte

This case does not provide any evidence of that, since both compilers
performed perfectly, and produced perfectly valid results.

You seem to be having a very difficult time with the notion of undefined
behavior. Look at the Standard's definition of it:

I don't have any difficulties. I am aware of it since the beginning.
Yes. The code is broken. You can establish this fact by referring to the
standard.

If the code is broken, why the compiler didn't told it to me?
This is the point. Is the compiler okay or can we do something about it?
But maybe this can be discussed somewhere else, maybe on comp.compilers.
It's my fault, I'm complaining about compiler behavior, and this is not
the place. Sorry.
Why not? If the documentation says that using it that way can give
unpredictable results, it's the user's fault for doing so.

You're right.
Bugs are unexpected or incorrect behavior. If the behavior is documented
as being unpredictable, and this is not out of line with the system
specs, there is no bug.

You're right.
This is exactly the case with the standard. It tells you what things you
can and cannot do if you want predictable, consistent results. If you go
ahead and do one of the things that gives unpredictable results anyway,
it's your own fault.

You don't really *know*, understand or even are able to manage or to avoid
the dark side until you are into it.
But you're right :)

Bye
Daniele
 
A

Arthur J. O'Dwyer

When you go "deeper than the standard" you no longer have any guarantees
about what your code will do, on this or any implementation, today or
tomorrow, etc. [...]

I want to go deeper than the standard for the only purpose of
learning, of being the best programmer I can be. I will never be using
such tricks, but now that I know them, I *understand* them better, and
eventually I am able to explain them to others exaustively, by giving them
true motivations and proofs.

That's a noble goal. You'll see, if you stick around (which I hope
you will), that many of the c.l.c "regulars" have anecdotes they've
collected about how optimization works on a Cray or a PDP-7, or how
"void main" melted the hard disk of a friend of a friend, et cetera.
Chris Torek is a great explainer of C foremost because he's a good
explainer, but secondmost [IMHO] because he's got some good experience
to back up his advice. Bottom line: Tricks are good to know.
But comp.lang.c is not the place to learn them. In c.l.c people
discuss regular old C, standard C, no-undefined-behavior-allowed C.
You wanna learn what makes your PC tick, ask around in a PC newsgroup
(or even better, get off Usenet and find a real expert to ask ;-).
Ditto the above, substituting appropriately "gcc", "stack optimization",
"VAXen", or whatever.
But once you *have* expanded your knowledge base, please don't
hesitate to use that knowledge wisely in this newsgroup or anywhere
else.

A compiler should point out when there could be an undefined behavior.

That would be incredibly annoying, taken as a real goal. I don't
*want* my compiler to produce a diagnostic message for

extern int foo(int a);
int foo(int a) { return a+1; }

and I don't think you really want that, either. (Possible signed
integer overflow in the expression 'a+1', just FYI.)
Taken literally, it's impossible. No computer can possibly tell
with 100% accuracy whether its given C program will produce UB or
not. That's a result of the Halting Problem, which you can Google
for, if you're interested in the theory behind all this.

As I said above, I don't want to write crap code with those tricks in it.
I stick to the standard when writing code, or at least I try to :)
I want to write the best code I can, and be aware of *why* that kind of code
results in undefined behavior. This is *understanding*. The other way is
just *believing*, and that could lead to mistakes too.

True. But in this case, there are a lot of places in the C standard
that just *are* undefined. No reason explicitly given. Most of the
time you can trace the origins of the "undefined behavior" marker back
to "existing practice" at the time the C standard was written, or to
efficiency concerns, or to disguised instances of the Halting Problem
(e.g., how do you tell whether a pointer is "wild" or not, while still
letting the programmer access the computer's whole memory?)
In these cases, you just *have* to "believe," as you put it. But
I certainly don't think of it as "belief," any more than I think of
my knowledge of the times-tables as "belief." Why should 7 times 8
equal 56? It's arbitrary. But it makes sense, and you can't do math
without it. The ISO Standard is the times-tables of C programming,
and if you have to memorize a few rules here and there, so be it.

Final note: Lest my tone sound a bit *too* fatalistic, remember
that you can always ask here as to "Why is such-and-such undefined
behavior?" and you'll be sure to get plenty of interesting answers.
I hope you'll keep up the learning.

-Arthur
 
K

Kevin Goodsell

tinybyte said:
If the code is broken, why the compiler didn't told it to me?

My compiler did. But this is not required, and it is not always possible
or desirable for a compiler to complain about possible undefined behavior.

-Kevin
 

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

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,265
Latest member
TodLarocca

Latest Threads

Top