---------- Forwarded message ----------
Date: Thu, 28 Feb 2002 13:39:36 +1300 (NZDT)
From: Richard A. O'Keefe <[EMAIL PROTECTED]>
To: [EMAIL PROTECTED], [EMAIL PROTECTED]
Subject: Re: Overzealous sign checking? (fwd)
Jon Wilson <[EMAIL PROTECTED]> wrote:
Great, I'm not going mad. That's exactly what I thought (in fact I
tried relaxquals). The new switch would be great, since what we've
seen is developers (half) blindly adding casts just to shut lint
up. That just hides the problems along with the innocuous cases, since
a casts says to the compiler and lint "I know what I'm doing" :->
If you have developers adding code (half) blindly, shutting lint up is
the least of your worries.
No, a cast does NOT say to the compiler "I know what I'm doing".
Casts are functions that _change_ things.
A cast doesn't say "I know what I'm doing",
it says "THIS is what I want you to do".
In particular, when you mix numbers of different signedness,
numbers of different size, or integers and floats, it is ALWAYS
confusing. There's a reason why Ada won't let you do that at all;
and once you get used to it, it's not a problem whacking in the
occasional type transfer function. In C, where the results are
not predictable (I mean that, the fact that the rules depend on
the relative sizes of the built in types, and that those relative
sizes can vary, and when you write the code you may not know what
they are, mean that you _can't_ predict what's going to happen
unless you are willing to restrict your code to a specific machine)
and where ANSI changed the rules, so that some C code uses
"value-preserving" rules and some C code uses "signedness-preserving
rules", it is especially confusing.
What we _really_ want here is a distinction between
- widening casts (a cast from type X to type Y where Y should
be able to hold every value of type X, e.g. float->double)
- narrowing casts (a cast from type X to type Y where some
values of type X can be (approximately) represented in type
Y, but some can't, e.g. double->float)
- transforming casts (especially pointer<->integer)
For argument's sake, suppose that
(/*@widen@*/ t) (e) widening cast to t
(/*@narrow@*/ t)(e) narrowing cast to t
(/*@transform@*/ t)(e) transforming cast to t
were annotations splint recognised.
Then unsigned short a; long b;
if ((/*@widen*/long)a < b) ...
would tell splint:
(1) Please check that long _is_ as wide as the type of 'a'
(2) The C compiler will convert the value of 'a' to 'long' type.
Also, a = (/*@narrow@*/ unsigned short)b;
would tell splint
(1) Please check that unsigned short _is_ narrower than b's type.
(2) The C compiler will convert the value of 'b' to 'unsigned short'.
(3) I claim that the value of b will fit,
but I might want to ask you -show-narrow to remind me where
the narrowing casts are, so that I can check them again.
A transforming cast (pointer<->pointer or pointer<->integral) would
(1) Please check that at least one of the types involved is a pointer type
and the other is pointer or integral.
(2) The C compiler will transform the value as shown
(3) Please warn me if the two types are not equally wide.
Something along these lines seems to be called for.
Actually, a question arises out of this: does Splint check for the
specific platform on which it was built, or an abstract C? As I
understand it, ANSI C just defines that sizeof( char ) <= sizeof(
short ) <= sizeof( long ), so for the (admittedly very odd) platform
where shorts are the same size as longs, the above code would indeed
I have used just such a platform, and there are plenty of DSPs where
everything is 24 bits.
Since there isn't an MS-DOS version of splint (and I still have a Mac at
home running Think C, where int defaults to 16 bits), since I have
accounts on several 64-bit machines but one of the machines on my desk
is only a 32-bitter, and since some of the software I'm trying to clean
up right now is used all over the world on all sorts of machines, having
splint check what the standard says is most useful to me.
However, I also have code for a specific platform, where I don't _care_ if
it even compiles anywhere else (because there is some platform-specific
assembly code). Having splint check for a particular set of sizes (NOT
necesarily the platform it was built on) is also obviously useful.
The most useful thing would be to check for either at the user's option.