printf example of why casting does not add clarity to the code. The bug, which is now fixed, simply highlights yet another peril of casting!
|
Implicit conversionsIn the C programming language, there are two kinds of conversions between expressions of different types. The first is supplied by the compiler itself. This is known as an implicit conversion. Here's an example of its use:
This kind of conversion is, of course, very natural. In fact,
an example of such a conversion occurs as early as page 12 of
K&R2 (where the conversion is from Explicit conversions (casts)There is, however, another way to convert an expression from one type into another; this second method is known as an explicit conversion, or cast. Here's an example of a cast:
This cast is a good one, by the way. There are circumstances in which explicit conversions, or casts, are a convenience which allows us to write better, cleaner code than would otherwise be the case. And yet casts are poorly understood. For some reason, casting is very popular among C programmers. It's as if a cast is a magic wand, that can magically transform one type into another, irrespective of semantics, logic, or common sense. In fact, there is very little (if any) sense in casting an expression for which a perfectly adequate implicit conversion already exists. There are very few exceptions to this rule of thumb. For example, consider this code:
Some compilers will complain about this code, arguing that
Casts as diagnostic suppressorsWe can often suppress the warning by using a cast:
The cast, in effect, tells the compiler: "I know what I'm doing! So shut up already!" Many C programmers do this (even those who only think that they know what they're doing). But is the cast justified? Well, in this case, there is a (perhaps spurious) justification, not in technical terms but purely in terms of getting a clean compilation. Alas, this excuse is used to justify a great many unnecessary casts. Yes, it's true that a nice clean compilation is good to see. But if we achieve that state only by gagging the compiler, without understanding the possible implications of such gagging, then we are running the risk of suppressing important and useful diagnostic information. In fact, we are merely indulging, and maybe even deceiving, ourselves. That first example is innocuous enough, but precisely the same logic (getting a clean compilation) can lure us into adding casts that actually hide problems, instead of fixing them. Casts as bug-hiders
The canonical example of this counter-productive diagnostic
suppression is the
Many years ago, before C was standardised by
ANSI,
But this ceased to be true
in 1989 -- a good 15 years ago as I write this. ANSI C
introduced the
Consequently, it is no longer necessary to cast
the return value of Clearly, if your code must be portable to compilers that pre-date the ANSI C Standard of 1989, then you have no choice but to cast. Fair enough. But this is true only in a vanishingly small number of cases. By far the majority of C code written today does not need to cater for prehistoric compilers. So we can, for the most part, ignore that reason for adding the cast, and look for other advantages and disadvantages.
All code should either do something good, or stop something
bad from happening. Now, what good does a
Now let's add those "self-documenting" casts:
Believe it or not, those casts are all "correct". And yes, the code works just fine. But suddenly the code isn't quite as easy to read, is it? So much for self-documentation.
Well, all right -- what about C++? In C++, it is necessary
to cast
Yes, that's absolutely true, but it's also utterly irrelevant.
C and C++ are very different languages! They are divided by a
common syntax. Nobody in their right mind would dream of saying
"I always wrap
If you are using a C++ compiler, then whether you like it or
not, you're writing C++ code, not C code. The rules
are different. If you wish to write C++ code that casts
If you wish to use C code in a C++ project, that's easy to do,
without casting So far, we have found no good reasons for casting. But are there any good reasons why we should not cast? Yes, there are.
Firstly, as I said earlier, all code should either do something
good or stop something bad happening. Casting
Secondly, casting
The bug in question is that of failing to provide a valid
function prototype for Let's consider a couple of ways in which things can go wrong. They both hinge on the wording of section 3.3.2.2 of the ANSI C Standard of 1989, which is as follows:
The following footnote applies to the above text:
Whilst footnotes are not normative text, they are useful in helping us to understand the intent of the committee, and sensible implementors will generally observe them, so it makes sense for us to take what they say very seriously.
Or, if you're not convinced by that, consider 3.1.6.2(2) of
the Standard, which says: "
Consider the situation from the point of view of the compiler
writer. As part of his implementation, he provides a standard
C library. As part of his C library, he implements the
When you write a C program that calls
In your program, let's just hypothesise for a moment that you
forgot to Under normal circumstances, this would require the compiler to issue a diagnostic. That's because the code would violate a constraint (see 3.3.16.1), and the compiler must issue a diagnostic if the program contains any constraint violations. But the cast forces the code to satisfy, rather than violate, the constraint. Consequently, no diagnostic is required.
The wording of the diagnostic is sometimes rather unfortunate.
Consider the wording of the
The wording is actually correct, because (by 3.3.2.2)
With the cast in place, you may not get a diagnostic at all. So what will happen? Well, of course, it might just work swimmingly well despite the lack of a prototype. But we can't know that. And even if it does, we have no guarantee that the same code will also work correctly if we were to switch to a different compiler.
What sorts of things can go wrong? I offer you two (but by
no means the only two) possibilities. Firstly, what if
What else could go wrong? Well, consider an implementation
which has separate registers for pointers and integers.
On such an implementation, the library code for
Again, the outcome is that your pointer object does not get the correct value.
As a consequence, we must conclude that to cast
Incidentally, precisely the same argument applies to any
function that returns a pointer; functions such as
Under what circumstances is casting correct?Very few. Casting is almost always wrong, and the places in which it is correct are rarely the ones you would guess.
One situation in which casting is a good idea is when you
are calling any of the functions prototyped in
What you don't have to do is worry about is casting to
When you are passing a value to the "tail" of a
variadic function, you must get the type just right, because
the normal promotions won't be done, which is in turn because
the compiler has no type information to work with. If the
variadic function takes a
For the same basic reason, you should cast a
SummaryOne of the characteristics of an expert C programmer is that he or she knows in what circumstances a cast is required and in what circumstances it is at best redundant and at worst a source of problems. Most programmers, however, are guilty of "cargo cult" programming where casts are concerned. Don't do that. Be an expert. Know why you are casting, whenever you cast, and remember when maintaining your own or other people's code that almost all casts in existing code should not actually be there. |