Martin Jørgensen said:
So I'm sorry to ask but what was wrong with doing this cast?
I can tell you why it's in my code: It's because I have some source code
from a guy who is a lot more experienced that me in C-programming. I can
change it however (and will do so, because I'm sure you're right even
though I don't know the explanation)...
Here's an explanation for you:
----------------------------------------------------------------------
Casting
Implicit conversions
In 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:
long int foo = 314159265L;
double bar = foo;
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 float to double). Such conversions are woven into the very fabric
of the language, and are so common and natural that we often fail to notice
them.
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:
IsLowerCase = islower((unsigned char)*p);
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:
#include <math.h>
long GetHypotenuse(long height, long base)
{
return sqrt(height * height + base * base);
}
Some compilers will complain about this code, arguing that sqrt returns a
double, and that assigning such a value to a long can result in a loss of
information. Well, that's true. But it's also true that, if such a loss of
information is intended, then there's no problem (because we're only
interested in the integer part of the result); and if such a loss of
information is not intended, then we really ought to pay attention to the
compiler's complaint.
Casts as diagnostic suppressors
We can often suppress the warning by using a cast:
#include <math.h>
long GetHypotenuse(long height, long base)
{
return (long)sqrt(height * height + base * base);
}
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 malloc function. As I'm sure you know, malloc is prototyped in
<stdlib.h> as void *malloc(size_t); -- that is, malloc is declared to be a
function taking a size_t as a parameter and returning a void pointer (i.e.
a pointer to an object whose type is not known).
Many years ago, before C was standardised by ANSI, malloc returned char *,
rather than void *; this was the most rational choice at the time, because
the void type didn't actually exist then (except, perhaps, as an extension
on some compilers). No implicit conversions between various pointer types
were supplied, so it was necessary to cast the return value of malloc into
the pointer type that you required:
#include <stdlib.h>
T *foo(int x)
{
T *new = (T *)malloc(sizeof *new); /* ancient history */
if(new)
{
new->zog = x;
}
return new;
}
But this ceased to be true in 1989 -- a good 15 years ago as I write this.
ANSI C introduced the void * pointer type, and gave it the very useful
property of being able to represent (without loss of information) any
object pointer whatsoever.
Consequently, it is no longer necessary to cast the return value of malloc.
But is it wise?
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 malloc cast do? One argument that is
occasionally raised in defence of the cast is that "the cast indicates the
type to which the return value is being assigned, so it makes the code more
self-documenting". But if that is true, then why do we not use casts more
often? Consider this example program from K&R2, page 12:
#include <stdio.h>
/* print Fahrenheit-Celsius table
for fahr = 0, 20, ..., 300; floating-point version */
main()
{
float fahr, celsius;
int lower, upper, step;
lower = 0; /* lower limit of temperature table */
upper = 300; /* upper limit */
step = 20; /* step size */
fahr = lower;
while(fahr <= upper) {
celsius = (5.0/9.0) * (fahr-32.0);
printf("%3.0f %6.1f\n", fahr, celsius);
fahr = fahr + step;
}
}
Now let's add those "self-documenting" casts:
#include <stdio.h>
/* print Fahrenheit-Celsius table
for fahr = 0, 20, ..., 300; floating-point version */
main()
{
float fahr, celsius;
int lower, upper, step;
lower = (int)0; /* lower limit of temperature table */
upper = (int)300; /* upper limit */
step = (int)20; /* step size */
fahr = (float)lower;
while((float)fahr <= (int)upper) {
celsius = (((float)
((float)5.0/(float)9.0)) *
(float)((float)
(((float)fahr-(float)32.0))));
(int)((int (*)(const char *, ...))
printf((const char *)"%3.0f %6.1f\n",
(float)fahr, (float)celsius));
fahr = (float)((float)fahr + (float)step);
}
}
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 void *
into an object pointer type, because the implementation is forbidden from
providing an implicit conversion.
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 printf in a function
named writeln, to maintain compatibility with Pascal", would they? But
because C and C++ have superficial similarities at the syntax level, some
people seem to think it's necessary to write C code that compiles with a
C++ compiler. Well, it isn't.
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 malloc (instead of using the perfectly serviceable new
allocator, or the STL's std::vector template), then that's entirely up to
you; good luck to you, and I wish you all joy in your use of C++. This
discussion is not directed at C++ users (except, perhaps, to remind them
that they are not writing in C, even if they think they are).
If you wish to use C code in a C++ project, that's easy to do, without
casting malloc. Use a C compiler to compile the C code (duh!), and then use
a linker to link the C code to the C++ code. C++ supplies the extern "C"
construct for precisely this purpose.
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 malloc does neither, so the cast is dead
code, and dead code has no place in a C program.
Secondly, casting malloc can actually hide a serious bug. Let me say quickly
that the cast doesn't cause the bug. But if the bug is there, the cast can
conceal its presence.
The bug in question is that of failing to provide a valid function prototype
for malloc. The function returns a void *, of course, but the C compiler
doesn't know that, unless you tell it. The best way to tell it is to
#include <stdlib.h> which provides a prototype which the compiler uses to
do type-checking and which it can exploit for code generation purposes.
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:
If the expression that precedes the parenthesized argument list
in a function call consists solely of an identifier, and if no
declaration is visible for this identifier, the identifier is
implicitly declared exactly as if, in the innermost block
containing the function call, the declaration
extern int identifier();
appeared.
The following footnote applies to the above text:
30. That is, a function with external linkage and no information
about its parameters that returns an int. If in fact it is
not defined as having type "function returning int ," the
behavior is undefined.
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: "All declarations that refer to the same object or function
shall have compatible type; otherwise the behavior is undefined."
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 malloc function. He almost certainly uses
his own C compiler to do this. The C compiler generates object code for
malloc, and this object code is placed into the standard C library, which
he then releases. Of course, this object code is based firmly on a malloc
that returns void *.
When you write a C program that calls malloc, the C implementation doesn't
have to compile malloc, because it is already compiled. All it has to do is
link the standard library (or at least, the parts of it that you actually
use) to your program. So nothing has changed, as far as the library is
concerned. The malloc function returns void *, and that's that.
In your program, let's just hypothesise for a moment that you forgot to
#include <stdlib.h>, so you have no prototype for malloc. The C compiler,
on encountering your malloc call, will therefore follow the wording of
3.3.2.2 of the ANSI C Standard, and presume that malloc returns int. It
will therefore generate code that assumes the return value of malloc is an
int. But you don't assign that value to an object of type int; rather, you
assign it to a pointer!
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 gcc diagnostic for this situation:
initialization makes pointer from integer without a cast
The wording is actually correct, because (by 3.3.2.2) gcc is right to assume
that an undeclared function returns int, and it's right in thinking that
you are trying to stick this int value into a pointer, but it's a mite
misleading, because it leads you to think that the correct fix is to add a
cast!
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 sizeof(void *) > sizeof(int)? This is
not just a theoretical possibility. It is certainly true for, say, typical
MS-DOS programs using a large memory model. Here's how it would break:
malloc returns a void *, but the compiler is required to turn this value
into an int. There aren't enough bits in the int to store the whole value,
so some information is lost. The int is then coerced back into a pointer,
but the lost information cannot now be retrieved, and if those lost bits
actually affect the value (say, they weren't just a bunch of 0s), then the
effect is that the pointer object receives an incorrect value -- that is,
instead of pointing to the allocated memory block, it points somewhere
completely different instead!
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 malloc will, of course, store the return value, a
pointer, in a pointer register. The compiler, however, doesn't know this
(because we didn't have a prototype for malloc), so it will actually
collect its return value from an integer register. This is a bit like going
to the wrong Post Office when collecting a parcel, seeing a parcel that
looks about the right size, and grabbing it on the assumption that it's the
right one. And yet it can't be the right one, because you're looking in the
wrong place.
Again, the outcome is that your pointer object does not get the correct
value.
As a consequence, we must conclude that to cast malloc is dangerous, and
that a competent C programmer simply should not do it.
Incidentally, precisely the same argument applies to any function that
returns a pointer; functions such as bsearch, strcpy, memmove, other
standard library functions returning pointers, and of course any of your
own custom functions that return pointers.
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 <ctype.h>. These functions take an int as
input, but the value stored in that int must either be EOF or a value that
can be represented in an unsigned char. Assuming that you're not daft
enough to pass EOF to such a function, then, it makes sense to cast the
value you are passing, unless you have some excellent reason for knowing
that it's bound to be in the appropriate range. So, for example, you could
reasonably call toupper in this way:
ch = toupper((unsigned char)ch);
What you don't have to do is worry about is casting to int (in this case).
Let the ordinary C promotion mechanism handle that for you.
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 T *, and you have a void * which you happen
to know points to an object of type T, that's fine, but you must cast the
pointer, to yield an expression of type T *. Conversely, if the function
expects a void *, you should cast to void * unless the pointer you have is
already of that type. Thus, when you call printf with a %p format
specifier, your matching pointer either should be a void * already, or
should be cast to one:
printf("Pointer value: %p\n", (void *)MyTPointer);
For the same basic reason, you should cast a size_t when printing it. I
generally use unsigned long for this purpose:
printf("Size: %lu\n", (unsigned long)sizeof MyTObject);
Summary
One 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.
----------------------------------------------------------------------
The full text of this article can be found on my Web site, at:
<
http://www.cpax.org.uk/prg/writings/casting.php>