Learning C with Older books ?.

L

Lawrence Kirby

On Mon, 20 Dec 2004 12:30:01 +0100, Charlie Gordon wrote:

....
Not at all !
It is not well known, and it is not portable.

I accept that I am only going on personal experience for the "well known"
bit and that may not be representative. If it isn't that is a pity. As for
your claim of non-portability it is just plain wrong, both in terms of
what the standard requires and, again in my experience, the lack of real C
compilers that are broken in this respect.
Furthermore, it is not reliable : it is blatant undefined behaviour (because of
integer overflow).

There is no integer overflow here. The rules for conversion from
any integer type to an unsigned integer type define clearly what happens
when the value being converted is not in the range of values representable
by the target type. It is never undefined.
(size_t)-1 is not necessarily the largest value size_t can represent :

It is easy to show from the rules in the standard that it must be.
just
think of the different representations C99 allows for integer arithmetic. It
may work for 2's complement, doesn't work for 1's complement (off by one), and
completely wrong for sign/magnitude.

Representation is irrelevant, the results of casts are defined in terms of
values NOT representations. If the result has a different bit pattern,
e.g. on a 1's complement architecture, the compiler must generate code
that makes the appropriate changes.
This loop is horrible, it should be banned. I rest my case.

Your case is based on incorrect assumptions. The C99 Standard says:

"When a value with integer type is converted to another integer type other
than _Bool, if the value can be represented by the new type, it is
unchanged.

Otherwise, if the new type is unsigned, the value is converted by
repeatedly adding or subtracting one more than the maximum value that can
be represented in the new type until the value is in the range of the new
type."

So if we convert the *value* -1 to an unsigned type the value isn't
representable directly in the new type so we apply the 2nd paragraph to
find out what to do. We can add one more than the maximum value that can
be represented by the unsigned type i.e. mathematically
-1 + (MAX_VALUE_OF_NEW_TYPE+1), which reduces to MAX_VALUE_OF_NEW_TYPE.
This is in the range of the new type so we are finished, bar noting that
size_t is defined as an unsigned integer type so this analysis applies to
the for() loop above.

The C90 wording is different but the result is the same.

Lawrence
 
L

Lawrence Kirby

Whether the warning is dumb depends on what you think the compiler is
diagnosing. I consider it poor style to compare a signed value and an
unsigned value, both because the results can be surprising and because
it isn't always obvious when/where it happens. That the code happens
to produce the results I expect (and, since the 'for' loop has an
empty body, how could it not?) does not effect the presence of the
warning condition.

It isn't up to the compiler to impose style considerations on the
programmer. It is dumb because a less dumb compiler could easily prove
that in this case i can never be negative so "surprising" results simply
can't happen, on any conforming C implementation. So there aren't even
portability issues.
Related question - if the code were

int main(){
int i = 0;

if( 0 ){
i += i++;
}
return 0;
}

would you want no diagnostic on the 'i += i++;' line, since
after all it won't be executed?

I have no problem with a diagnostic on that, although most compilers don't
diagnose i+=i++ anywhere. i+=i++ is a bug if it gets executed and serves
no purpose if it can never be executed. There is no circumstance under
which it can be correct and sensible. That makes it quite different to the
loop example.

Lawrence
 
J

James Dennett

Lawrence said:
It isn't up to the compiler to impose style considerations on the
programmer.

To issue a warning doesn't impose things on you. It gives you
information you can choose to act on, or choose not to act on.
It is dumb because a less dumb compiler could easily prove
that in this case i can never be negative so "surprising" results simply
can't happen, on any conforming C implementation. So there aren't even
portability issues.

I think it's reasonable for compilers to warn when you use error-prone
styles, even if they don't trigger errors in a given situation. YMMV.

-- James
 
L

Lawrence Kirby

On Tue, 21 Dec 2004 18:24:58 -0800, James Dennett wrote:

....
To issue a warning doesn't impose things on you. It gives you
information you can choose to act on, or choose not to act on.


In practice people want to produce code that compiles without warnings.
There may even have to work under policies where this is the case. In
practical terms having a compilation generate a buch of spuious warnings
can easily obscure a serious one, and make it more difficult to pinpoint
where the start of the trouble is.

I think it's reasonable for compilers to warn when you use error-prone
styles, even if they don't trigger errors in a given situation. YMMV.

Do you allow production code to compile with warnings?

Lawrence
 
A

Alwyn

In practice people want to produce code that compiles without warnings.

That's pretty easy to do, wouldn't you say?
There may even have to work under policies where this is the case. In
practical terms having a compilation generate a buch of spuious warnings
can easily obscure a serious one, and make it more difficult to pinpoint
where the start of the trouble is.

It's often in the nature of these things that only a human can determine
what is a really serious warning. C is an unsafe language, and one has to
learn to live, somehow, with its lack of safety. If you want a safer
language, I could provide you with a whole list, but they all have their
drawbacks. What you lose on the swings, you gain on the roundabouts.

Do you allow production code to compile with warnings?

Almost all the open-source software I build compiles with such warnings.
It seems to be a fact of life.


Alwyn
 
J

James Dennett

Lawrence said:
On Tue, 21 Dec 2004 18:24:58 -0800, James Dennett wrote:

...





In practice people want to produce code that compiles without warnings.
There may even have to work under policies where this is the case. In
practical terms having a compilation generate a buch of spuious warnings
can easily obscure a serious one, and make it more difficult to pinpoint
where the start of the trouble is.

That's why we make code compile warning-free. Compilers usually
offer options to turn off particular warnings, if your house
style requires warning free compilation (as it should) but allows
styles which trigger warnings with a given compiler.
Do you allow production code to compile with warnings?

No. But then I find that the natural style of most people
with whom I work doesn't tend to trigger warnings, except
sometimes when working with MSVC++6 where the standard headers
generate warnings which have to be #pragma'd off.

-- James
 
I

infobahn

Lawrence said:
On Tue, 21 Dec 2004 18:24:58 -0800, James Dennett wrote:



Do you allow production code to compile with warnings?

Sometimes, yes. Some constructs cause the compiler to issue a warning
under circumstances where the code may, or may not, be safe. To give
a simple example, some compilers will warn against this code:

#include <stdlib.h>

int rnd_in_range(int low, int high)
{
int range = high + 1 - low;
double d = range * (rand() / (RAND_MAX + 1.0));
return d + low;
}

The warning is, of course, that significant information might
be lost when a double is converted to an int return value.

Under these circumstances, I have several options:

1) I can silence the warning with a cast, but that's never a good
idea.

2) I can turn that warning off globally, but then I will not be
warned when I *accidentally* lose significant information via
such a conversion.

3) I can litter my code with compiler-specific pragmas to turn
warnings on and off as required, but then I'm either nailed
to one compiler or forced to do a lot of #ifdef-ing.

4) I can put up with the warning, adding a comment to the code
to explain that *IN THIS CASE* the warning has been noted and
the code is known to be correct.

So yes, sometimes I let production code compile even with warnings.
I'd prefer not to, but sometimes it is the least of several evils.
 
L

Lawrence Kirby

Lawrence Kirby wrote:
....


Sometimes, yes. Some constructs cause the compiler to issue a warning
under circumstances where the code may, or may not, be safe. To give
a simple example, some compilers will warn against this code:

#include <stdlib.h>

int rnd_in_range(int low, int high)
{
int range = high + 1 - low;
double d = range * (rand() / (RAND_MAX + 1.0));
return d + low;
}

The warning is, of course, that significant information might
be lost when a double is converted to an int return value.

Under these circumstances, I have several options:

1) I can silence the warning with a cast, but that's never a good
idea.

Never say never. Like goto, avoid it except where it is the best tool for
the job. A cast looks to me to be the sensible solution here.

1. Arithmetic casts aren't particularly dangerous, they don't allow you to
do things that you can't do with implicit conversions. Contrast that to
pointer casts which introduce numerous possibilities for undefined
behaviour.

2. If you use a cast then do comment it. A coding style that requires
EVERY cast to be justified in a comment would be perfectly reasonable.
2) I can turn that warning off globally, but then I will not be
warned when I *accidentally* lose significant information via such a
conversion.

Yes, the sledgehammer to crack a nut approach, The specifically targetted
cast is a much better solution, except perhaps in rare cases where you
have a lot of nuts to crack at once.
3) I can litter my code with compiler-specific pragmas to turn
warnings on and off as required, but then I'm either nailed to one
compiler or forced to do a lot of #ifdef-ing.

Messy, especially when there is a simple and clear solution that works on
all C compilers. By "works" I mean that the code is still correct and
portable. A cast of course isn't guaranteed to silence compiler warnings,
but I'm happy to ignore that problem until I encounter it.
4) I can put up with the warning, adding a comment to the code
to explain that *IN THIS CASE* the warning has been noted and the
code is known to be correct.

Then we have the problem of spurious compiler warnings hiding important
ones. And that isn't just different compiler warnings. What if you got a
"Significant information lost" warning indicating a real problem in
amongst all the spurious ones - would you spot it? You might just as well
disable the warning. The comment is OK, but it is better to comment
something local rather than getting the warning and then having to back
reference it to find the comment.
So yes, sometimes I let production code compile even with warnings. I'd
prefer not to, but sometimes it is the least of several evils.

IMO the warning is a bigger evil and alternatives such as casting are,
no can be, lesser evils than you make out.

Lawrence
 
R

Richard Bos

Lawrence Kirby said:
Never say never. Like goto, avoid it except where it is the best tool for
the job. A cast looks to me to be the sensible solution here.

True. And that's coming from me, who thinks spurious casts are like a
high-voltage sign on a penlight battery.
1. Arithmetic casts aren't particularly dangerous, they don't allow you to
do things that you can't do with implicit conversions. Contrast that to
pointer casts which introduce numerous possibilities for undefined
behaviour.

2. If you use a cast then do comment it. A coding style that requires
EVERY cast to be justified in a comment would be perfectly reasonable.

Make that every cast except those in calls to <ctype.h> functions, in
comparison functions for qsort() and friends, and null pointer casts in
variadic functions, and I'd quite agree.

Richard
 
D

Daniel Vallstrom

Tim said:
(e-mail address removed) (Dan Pop) writes:

[snip discussion about the pros and cons of gcc's -W and -Wall warning
options]
So, what's wrong with -Wall, from your point of view?


Ok, the $64 question. Again for the sake of discussion let's take
the list below as the set of warnings covered by -Wall:

-Wchar-subscripts - using values of type 'char' to subscript
-Wcomment - when /* appears in a comment string (& more)
-Wformat - check printf et al format strings against args
-Wnonnull - NULL arg to "nonnull" attribute parameters
-Wimplicit - decl w/o type, or function call w/o decl
-Wmain - type of main() is suspicious
-Wmissing-braces - initializers not fully bracketed
-Wparentheses - eg, if(x=a+b). also, some unpaired if's
-Wsequence-point - possible violations of sequence point rules
-Wreturn-type - non-void function 'return;'; no function type
-Wswitch - switch( ENUM ) that doesn't cover all cases
-Wswitch-default - switch() w/o 'default:' case
-Wswitch-enum - switch( ENUM ) cases != ENUM values
-Wtrigraphs - if any trigraphs are encountered
-Wunused - unused variable, value, label, static function
-Wunused-parameter - unused parameter
-Wuninitialized - variable might be used without having been set
-Wunknown-pragmas - unrecognized #pragma
-Wstrict-aliasing - code that may break with -fstrict-aliasing

I realize this set probably doesn't exactly match the gcc that anyone
is currently using, but I think it will help the discussion if we
proceed under the assumption that these conditions make up the current
set of -Wall conditions.

I would break these down into four categories - the always useful, the
usually useful, the probably useful, and the objectionable.
[snip]

The 'objectionable' are those that might get used as diagnostic tools
from time to time but are turned off during all regular compiles. The
objectionable ones are:

-Wparentheses - eg, if(x=a+b). also, some unpaired if's
-Wswitch-default - switch() w/o 'default:' case


Discussion for -Wswitch-default:

-Wswitch-default
Warn whenever a "switch" statement does not have a
"default" case.

First of all, either -Wswitch or -Wswitch-enum seems like a more
accurate diagnostic tool when the switch() expression is of an
enumerated type [2].

Second, the decision to put in a 'default:' case is a programming
practice that is appropriate in some circumstances but not in others.
Apparently there is no way to locally override the warning for
switch() statements where the 'default:' is judged better left out.

Third, automatically adding a 'default:' to every switch() can reduce
the value of -Wswitch/-Wswitch-enum. It can be useful to leave off
the 'default:' so that when additional values are added to an
enumerated type then switch() statements on values of that type will
be flagged. Including -Wswitch-default will thwart this programming
practice.

All good points. However, obviously "-Wswitch-default is included in
-Wall" from the gcc documentation means that -Wswitch-default is *not*
included in -Wall;p JK But in fact it seems to be the case that
-Wswitch-default is indeed not included in -Wall --- thankfully. Testing
it I get no warnings when using the -Wall flag while I get tons of
warnings when I explicitly add the -Wswitch-default flag.


Here is an old post to this thread that I made through google that
doesn't seem to show up in any of the non-google news-readers I use.
(Not that you missed much if you haven't received it;)

http://groups-beta.google.com/group..._doneTitle=Back+to+Search&&d#6bbce6900ea6d6e4


Daniel Vallstrom
 
T

Tim Rentsch

Daniel Vallstrom said:
Tim said:
(e-mail address removed) (Dan Pop) writes:

[snip discussion about the pros and cons of gcc's -W and -Wall warning
options]
So, what's wrong with -Wall, from your point of view?


Ok, the $64 question. Again for the sake of discussion let's take
the list below as the set of warnings covered by -Wall:

-Wchar-subscripts - using values of type 'char' to subscript
-Wcomment - when /* appears in a comment string (& more)
-Wformat - check printf et al format strings against args
-Wnonnull - NULL arg to "nonnull" attribute parameters
-Wimplicit - decl w/o type, or function call w/o decl
-Wmain - type of main() is suspicious
-Wmissing-braces - initializers not fully bracketed
-Wparentheses - eg, if(x=a+b). also, some unpaired if's
-Wsequence-point - possible violations of sequence point rules
-Wreturn-type - non-void function 'return;'; no function type
-Wswitch - switch( ENUM ) that doesn't cover all cases
-Wswitch-default - switch() w/o 'default:' case
-Wswitch-enum - switch( ENUM ) cases != ENUM values
-Wtrigraphs - if any trigraphs are encountered
-Wunused - unused variable, value, label, static function
-Wunused-parameter - unused parameter
-Wuninitialized - variable might be used without having been set
-Wunknown-pragmas - unrecognized #pragma
-Wstrict-aliasing - code that may break with -fstrict-aliasing

[discussion of objectionable warnings snipped]

All good points. However, obviously "-Wswitch-default is included in
-Wall" from the gcc documentation means that -Wswitch-default is *not*
included in -Wall;p JK But in fact it seems to be the case that
-Wswitch-default is indeed not included in -Wall --- thankfully. Testing
it I get no warnings when using the -Wall flag while I get tons of
warnings when I explicitly add the -Wswitch-default flag.

Oh, the documentation for what conditions -Wall includes is wrong?
Please add that to my list of reasons not to use -Wall.

(Thank you Daniel for the followup.)
 

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,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top