writing library functions and pointers?

K

Keith Thompson

Morris Keesan said:
k&r errata #142:
On the other hand, pre-ANSI, the cast was
necessary [from malloc return to other pointer types]

I've seen this claim many times, but I've never used any compiler for
which the cast was necessary, nor seen any definition of the language
in which it was necessary, including, among others, the language
definition as published in K&R 1, or the (almost identical) C Language
documentation in the version 6 or 7 Unix documentation.

K&R1 Appendix A 14.4, "Explicit pointer conversions" (page 210):

Certain conversions involving pointers are permitted but have
implementation-defined aspects. They are all specified by means
of an explicit type-conversion operator ([sections] 7.2 and 8.7).

Some compilers may have been lax enough to permit, for example,
converting from char* (the result type of malloc) to int* without a
cast, but K&R1 strongly implies that a cast was required. (There was
no void* type.)

An example on the next page is:

extern char *alloc();
double *dp;

dp = (double *) alloc(sizeof(double));
*dp = 22.0 / 7.0;

Going back to 7.14, (page 191), the description of assignment is
somewhat ambiguous:

In the simple assignment with =, the value of the expression
replaces that of the object referred to by the lvalue. If both
operands have arithmetic type, the right operand is converted to
the type of the left preparatory to the assignment.

Note that it doesn't mention implicit pointer conversion. But later
in the same section:

The compilers currently allow a pointer to be assigned to an
integer, an integer to a pointer, and a pointer to a pointer of
another type. The assigment is a pure copy operation, with no
conversion. This usage is nonportable, and may produce pointers
which cause addressing exceptions when used. However, it is
guaranteed that assignment of the constant 0 to a pointer will
produce a null pointer distinguishable from a pointer to any
object.

My reading of this is that implicit pointer conversion is not
permitted by the language, but compilers allow it anyway.

(Of course a lot of this changed with the 1989/1990 and 1999
C standards.)
 
B

Ben Bacarisse

Morris Keesan said:
k&r errata #142:
On the other hand, pre-ANSI, the cast was
necessary [from malloc return to other pointer types]

I've seen this claim many times, but I've never used any compiler for
which the cast was necessary, nor seen any definition of the language
in which it was necessary, including, among others, the language
definition as published in K&R 1, or the (almost identical) C Language
documentation in the version 6 or 7 Unix documentation.

You've had some references, but there are also real examples. I
worked on one machine that was (16-bit) word-addressed. To represent
character pointers, the pointer to the containing word was shifted
left by 1 and the bottom bit was used to pick which byte was being
addressed.

When compiling:

int i;
char *ip = (char *)&i;
double *dp = (double *)malloc(sizeof *dp);

the casts generated code (a shift left and a shift right). You might
wonder why the compiler could not just generate this code without the
cast but remember this is K&R C where there are no function
prototypes. Faced with:

use_pointer(malloc(16));

the compiler has no idea what type of pointer the function expects.
Some of the Unix utilities were a challenge to port.
 
M

Morris Keesan

Morris Keesan said:
k&r errata #142:
On the other hand, pre-ANSI, the cast was
necessary [from malloc return to other pointer types]

I've seen this claim many times, but I've never used any compiler for
which the cast was necessary, nor seen any definition of the language
in which it was necessary, including, among others, the language
definition as published in K&R 1, or the (almost identical) C Language
documentation in the version 6 or 7 Unix documentation.

You've had some references, but there are also real examples. I
worked on one machine that was (16-bit) word-addressed. To represent
character pointers, the pointer to the containing word was shifted
left by 1 and the bottom bit was used to pick which byte was being
addressed.

When compiling:

int i;
char *ip = (char *)&i;
double *dp = (double *)malloc(sizeof *dp);

the casts generated code (a shift left and a shift right). You might
wonder why the compiler could not just generate this code without the
cast but remember this is K&R C where there are no function
prototypes.

This has nothing to do with function prototypes. If malloc were
properly declared as returning a (char *), then the
conversion-by-assignment which would be generated by

double *dp = malloc(sizeof *dp);

should be the same conversion generated by the cast, i.e. conversion
of (char *) to (double *). If malloc were not declared, the conversion
would have been the (incorrect) (int) to (double *). Are you saying
that on this word-addressed machine, assigning a (char *) to a (double *)
without a cast did not generate the code to do a type conversion?

I also worked on a word-addressed machine (a Honeywell Level 6), where
(char *) was twice as large as all other pointers, because the C compiler
implemented (char *) as an (int *) plus an extra word indicating high or
low byte. As you say below, some code was a "challenge to port", since
so many people wrote code which assumed that all pointers had identical
representations. But even on that machine, assigning a (char *) to
any other data-pointer type caused the correct conversion to occur
(assuming that the (char *) were properly aligned), just as if the
conversion were done by a cast before the assignment.
Faced with:

use_pointer(malloc(16));

the compiler has no idea what type of pointer the function expects.
Some of the Unix utilities were a challenge to port.

Yes, and in this case, the result of malloc would have to be
converted to the correct pointer type by an explicit cast, but this
is totally different from the case of an assignment, where the
left-hand side of the assignment operator imposes a type which must
be converted to (except in the cases cited by Keith Thompson, for
which thanks, Keith).
 
K

Keith Thompson

Morris Keesan said:
Morris Keesan said:
On Mon, 06 Jul 2009 09:47:26 -0400, (e-mail address removed)0m

k&r errata #142:
On the other hand, pre-ANSI, the cast was
necessary [from malloc return to other pointer types]

I've seen this claim many times, but I've never used any compiler for
which the cast was necessary, nor seen any definition of the language
in which it was necessary, including, among others, the language
definition as published in K&R 1, or the (almost identical) C Language
documentation in the version 6 or 7 Unix documentation.

You've had some references, but there are also real examples. I
worked on one machine that was (16-bit) word-addressed. To represent
character pointers, the pointer to the containing word was shifted
left by 1 and the bottom bit was used to pick which byte was being
addressed.

When compiling:

int i;
char *ip = (char *)&i;
double *dp = (double *)malloc(sizeof *dp);

the casts generated code (a shift left and a shift right). You might
wonder why the compiler could not just generate this code without the
cast but remember this is K&R C where there are no function
prototypes.

This has nothing to do with function prototypes. If malloc were
properly declared as returning a (char *), then the
conversion-by-assignment which would be generated by

double *dp = malloc(sizeof *dp);

should be the same conversion generated by the cast, i.e. conversion
of (char *) to (double *). If malloc were not declared, the conversion
would have been the (incorrect) (int) to (double *). Are you saying
that on this word-addressed machine, assigning a (char *) to a (double *)
without a cast did not generate the code to do a type conversion?

(Note again that we're talking about K&R C, not modern C.)

As I posted recently, K&R1 doesn't say that this will result in a
conversion. Page 192:

The compilers currently allow a pointer to be assigned to an
integer, an integer to a pointer, and a pointer to a pointer of
another type. The assigment is a pure copy operation, with no
conversion. This usage is nonportable, and may produce pointers
which cause addressing exceptions when used. However, it is
guaranteed that assignment of the constant 0 to a pointer will
produce a null pointer distinguishable from a pointer to any
object.

And for scalar initialization (page 198):

The initial value of the object is taken from the expression; the
same conversions as for assignment are performed.

If
double *dp = malloc(sizeof *dp);
worked properly on a system where double* and char* were of different
sizes, then apparently the compiler behaved differently from what K&R1
described.
I also worked on a word-addressed machine (a Honeywell Level 6), where
(char *) was twice as large as all other pointers, because the C compiler
implemented (char *) as an (int *) plus an extra word indicating high or
low byte. As you say below, some code was a "challenge to port", since
so many people wrote code which assumed that all pointers had identical
representations. But even on that machine, assigning a (char *) to
any other data-pointer type caused the correct conversion to occur
(assuming that the (char *) were properly aligned), just as if the
conversion were done by a cast before the assignment.

Again this differs from the description in K&R1.
Yes, and in this case, the result of malloc would have to be
converted to the correct pointer type by an explicit cast, but this
is totally different from the case of an assignment, where the
left-hand side of the assignment operator imposes a type which must
be converted to (except in the cases cited by Keith Thompson, for
which thanks, Keith).

Right. In modern C with prototypes, function arguments are implicitly
converted to the parameter type as if by assignment. In K&R C, the
parameter types weren't visible to the caller, so the only conversions
that occured were float to double, char and short to int, and array to
pointer. (Modern C behaves similarly when no prototype is visible or
for variadic functions.)
 
B

Ben Bacarisse

Morris Keesan said:
Morris Keesan said:
On Mon, 06 Jul 2009 09:47:26 -0400, (e-mail address removed)0m

k&r errata #142:
On the other hand, pre-ANSI, the cast was
necessary [from malloc return to other pointer types]

I've seen this claim many times, but I've never used any compiler for
which the cast was necessary, nor seen any definition of the language
in which it was necessary, including, among others, the language
definition as published in K&R 1, or the (almost identical) C Language
documentation in the version 6 or 7 Unix documentation.

You've had some references, but there are also real examples. I
worked on one machine that was (16-bit) word-addressed. To represent
character pointers, the pointer to the containing word was shifted
left by 1 and the bottom bit was used to pick which byte was being
addressed.

When compiling:

int i;
char *ip = (char *)&i;
double *dp = (double *)malloc(sizeof *dp);

the casts generated code (a shift left and a shift right). You might
wonder why the compiler could not just generate this code without the
cast but remember this is K&R C where there are no function
prototypes.

This has nothing to do with function prototypes. If malloc were
properly declared as returning a (char *), then the
conversion-by-assignment which would be generated by

double *dp = malloc(sizeof *dp);

should be the same conversion generated by the cast, i.e. conversion
of (char *) to (double *). If malloc were not declared, the conversion
would have been the (incorrect) (int) to (double *). Are you saying
that on this word-addressed machine, assigning a (char *) to a (double *)
without a cast did not generate the code to do a type conversion?

It did not and the legalistic reason may have been because that is
what is mandated by K&R (I think Keith has posted the reference
elsewhere in this thread) but I suspect the reason compiler writer
were happy to go along with this is that is gives pointers a simple
interface with the old un-prototyped functions: initialisation,
assignment and parameter passing are all conversion free copy
operations.
I also worked on a word-addressed machine (a Honeywell Level 6), where
(char *) was twice as large as all other pointers, because the C compiler
implemented (char *) as an (int *) plus an extra word indicating high or
low byte. As you say below, some code was a "challenge to port", since
so many people wrote code which assumed that all pointers had identical
representations. But even on that machine, assigning a (char *) to
any other data-pointer type caused the correct conversion to occur
(assuming that the (char *) were properly aligned), just as if the
conversion were done by a cast before the assignment.


Yes, and in this case, the result of malloc would have to be
converted to the correct pointer type by an explicit cast, but this
is totally different from the case of an assignment, where the
left-hand side of the assignment operator imposes a type which must
be converted to (except in the cases cited by Keith Thompson, for
which thanks, Keith).

Yes, it is a separate case, but I was trying to flesh out the logic
behind K&R's view that assignment does no conversion. My feeling is
that this is done to give pointers a simple model -- all conversions
must be explicit and all duplication operations (assignment,
initialastion and argument passing) are plain copies.

There is (was?) another way open: assignment and initialisation could
convert (along with the other know-type case: function return) and a
promotion be added to convert all pointer arguments to the widest and
most general pointer type. Of course, in many cases this would
perform unnecessary operations. Anything between these two seems to
me to be an unsatisfactory mixed bag.

In the other case where the right type is known, function return, K&R
give explicit advice: use a cast as this "represents the safest course
for the future" (sec 6.5 p 134).
 
R

Richard Bos

Kaz Kylheku said:
The void * type was imitated from C++, which doesn't have the gratuitous hole
that void * can be converted to another pointer-to-object type without a cast:

double x;
void *pv = &x;
int *pi = pv; /* diagnostic required in C++, not in C. */
*pi = 0;

This bit of nonsense should be removed from the C language.

The nonsense is in C++. It implies that assigning one way is dangerous,
while the other way is safe. This is clearly misinformation. It's not
either way which is dangerous or safe, but a wrong combination of both.
Therefore, the cast should be required on both, or on neither. Demanding
it on only one is misleading.
What's more, requiring the cast on the side that malloc() is on is
doubly bogotic, since assigning the return value from malloc() to _any_
object pointer is safe by design.
What it means is that you can write some unsafe conversions in C,
void * conversions can easily go awry. For instance, in generic containers,
callbacks and such.

void fun(void *);

{
struct foo args;
/* ... */
callback_interface(fun, &args);
/* ... */
}
void fun(void *pvargs)
{
struct bar *args = pvargs; /* oops! */
}

Yes, but how do you know that the oops is in _that_ line? Maybe fun
should have been called with a bar * instead, and the real error was
passing it a foo * in the first place. So the cast should have been
here, as well:

callback_interface(fun, (void *)&args);
The point of using lint is to obtain such spurious warnings.

You investigate whether or not the potentially unsafe conversion is in fact
safe, and then you fix the code, if necessary, or add a cast to give the
conversion blessing.

Except that you know damned well that the solution employed in most
cases will be to add the cast _without_ investigation, or even without
running lint. Old C code, or written by C++ programmers, is all too
often riddled with such spurious casts, which only get in the way of
checking for _real_ points of danger.

Richard
 
M

Morris Keesan

As I posted recently, K&R1 doesn't say that this will result in a
conversion. Page 192:

The compilers currently allow a pointer to be assigned to an
integer, an integer to a pointer, and a pointer to a pointer of
another type. The assigment is a pure copy operation, with no
conversion. This usage is nonportable, and may produce pointers
which cause addressing exceptions when used. However, it is
guaranteed that assignment of the constant 0 to a pointer will
produce a null pointer distinguishable from a pointer to any
object.

I meant to reply to this a couple of weeks ago. Thanks for posting
this citation, Keith. I stand corrected on my earlier claim that
casting the return value of malloc was never required.
 

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,756
Messages
2,569,540
Members
45,024
Latest member
ARDU_PROgrammER

Latest Threads

Top