implicit type conversions

B

buda

Let me see if I got this :)
1. I know the rules for type conversions in arithmetic expressions
2. I know that an implicit type conversion is done at assignment, so
float x = 1.23;
int t = (int) x;
is equivalent to
int t = x;
(could the latter produce a warning on some complier?)
3. I know that implicit conversions take place with function arguments, but
am a bit shaky here. I suppose that passing a char to a function accepting
int or long will always work, and those sort of conversions seem to make
sense. But what sort of things won't work here? For example, the FAQ says
that the NULL pointer *must* be cast to the appropriate type when sent to a
function as an argument (so, for example, time(NULL) is incorrect, and
time( (time_t *) NULL) is correct?), which seems to indicate that
conversions from (void *) to (some_type *) in function argument lists need
not be (isn't) implicit. If someone would clarify this, I'd be most grateful
(like which sort of things cause warnings, which need be cast and the like).

Thank you.
 
A

Arthur J. O'Dwyer

Let me see if I got this :)
1. I know the rules for type conversions in arithmetic expressions

No, probably not. But they're quite simple as long as you stay
away from corner cases. Here's the rule of thumb: Unsigned trumps
signed. Long trumps int. Short and char are automatically promoted
to int (signed or unsigned, depending on the implementation limits).
Void pointers can be converted to and from any other data pointer
type silently and automatically. You can assign between floating-point
and integer types silently (though it's not a good idea IMHO because
it will usually cause lots of spurious compiler warnings). Everything
else probably requires a cast.
For the real rules, read the Standard.
2. I know that an implicit type conversion is done at assignment, so
float x = 1.23;
int t = (int) x;
is equivalent to
int t = x;
(could the latter produce a warning on some complier?)

Sure. On the DS9000, it produces "Warning: This program may or
may not be standard C." More helpfully, most compilers will warn
you about a possible "loss of precision" during the conversion.
Neither warning is required by the Standard.
3. I know that implicit conversions take place with function arguments, but
am a bit shaky here. I suppose that passing a char to a function accepting
int or long will always work, and those sort of conversions seem to make
sense. But what sort of things won't work here?

Passing negative numbers to functions expecting 'unsigned' types
is often dangerous, though well-defined by the Standard. Passing
anything to a variadic function like 'printf' requires careful thought.
For example, the FAQ says
that the NULL pointer *must* be cast to the appropriate type when sent to a
function as an argument (so, for example, time(NULL) is incorrect, and
time( (time_t *) NULL) is correct?),

Wrong. NULL must be cast *only* when passing it to a variadic function
expecting a particular type of pointer. For example,

printf("%p\n", NULL); is obviously wrong if #define NULL 0
printf("%p\n", (void*)NULL); is correct in all cases
which seems to indicate that
conversions from (void *) to (some_type *) in function argument lists need
not be (isn't) implicit. If someone would clarify this, I'd be most grateful
(like which sort of things cause warnings, which need be cast and the like).

(void*) and (some_type*) are mutually convertible whenever 'some_type'
is a data type. (That is, a conversion between (void*) and (int(*)())
requires a cast, and isn't even guaranteed to work at all. This is
because (int(*)()) is a function pointer type, not a data pointer type.)

Read the FAQ again, and then check the standard (Google "N869" for
the last public draft thereof).

HTH,
-Arthur
 
E

Eric Sosman

buda said:
Let me see if I got this :)
1. I know the rules for type conversions in arithmetic expressions
2. I know that an implicit type conversion is done at assignment, so
float x = 1.23;
int t = (int) x;
is equivalent to
int t = x;
(could the latter produce a warning on some complier?)

Yes. The Standard requires diagnostics for some violations,
but does not forbid diagnostics even when no violation has been
detected. Years ago, I used a compiler that complained about

float f = 0.0;

.... because of the potential "loss of precision" involved in
shortening the `double' to `float'.

Sometimes the extra diagnostics are helpful, and sometimes
they are merely noise.
3. I know that implicit conversions take place with function arguments, but
am a bit shaky here. I suppose that passing a char to a function accepting
int or long will always work, and those sort of conversions seem to make
sense. But what sort of things won't work here? For example, the FAQ says
that the NULL pointer *must* be cast to the appropriate type when sent to a
function as an argument (so, for example, time(NULL) is incorrect, and
time( (time_t *) NULL) is correct?), which seems to indicate that
conversions from (void *) to (some_type *) in function argument lists need
not be (isn't) implicit. If someone would clarify this, I'd be most grateful
(like which sort of things cause warnings, which need be cast and the like).

If the function has a prototype that describes the types of
all its formal parameters, the argument values you supply will be
converted to the types the function expects. For example, if you
have #include'd <time.h>, the time() function has been declared
as taking one argument of type `time_t*', so the compiler has the
information it needs to convert a plain `NULL' to `(time_t*)NULL'.
In this situation, time(NULL) and time( (time_t*)NULL ) are
equivalent.

However, there are three situations where the compiler lacks
the information it would need to do such conversions automatically:

- If the function has not been declared prior to its use,
the compiler knows nothing about what parameters it expects.

- If the function has been declared but without a prototype
describing the arguments (e.g., `double trouble();'), the
compiler once again has no knowledge of what's expected.

- If the function has been declared with a prototype but is
a "variadic" function (that is, the prototype ends with
`,...)'), the compiler has no knowledge about the parameters
that correspond to the `...' piece. (It does, of course,
know about the parameters that precede it.)

In these cases, you must either write the conversions explicitly or
accept the "default argument promotions." Since `NULL' can be
either `(void*)0' or plain `0' (or other equivalent forms), you
don't really know exactly what you've written when you write an
unadorned `NULL', so the conversion must be made explicitly.

Recommended practices to ease the pain:

- Always declare functions before using them. (In fact, this
is mandatory under the latest "C99" Standard, and the compiler
is required to complain if you fail to do this.)

- Use argument prototypes in function declarations, so the
compiler knows as much as possible about the function. This
lets it make the proper conversions automatically, and lets
it catch some kinds of mistakes.

- The best way to declare a library function -- from the Standard
library or from any other -- is to #include the appropriate
header.

If you always declare functions and always use prototypes, the
only remaining situation you need to worry about is the arguments
that match the `...' parameters of variadic functions.
 
C

CBFalconer

Eric said:
.... snip ...

- The best way to declare a library function -- from the
Standard library or from any other -- is to #include the
appropriate header.

If you always declare functions and always use prototypes,
the only remaining situation you need to worry about is the
arguments that match the `...' parameters of variadic functions.

So it appears to me that if the system declares NULL as (void *)0
then the code:

printf("%p\n", NULL);

is well defined, but if the system simply declares NULL as 0 there
could be a problem. This is fairly obvious in this case, but may
not be if the pointer variable is passed in. The problem will
_probably_ not arise if the sizes of int and void* are the same.
This could be a sneaky bug, and should encourage always declaring
NULL as (void*)0.
 
A

Arthur J. O'Dwyer

]
So it appears to me that if the system declares NULL as (void *)0
then the code:

printf("%p\n", NULL);

is well defined, but if the system simply declares NULL as 0 there
could be a problem. This is fairly obvious in this case, but may
not be if the pointer variable is passed in. The problem will
_probably_ not arise if the sizes of int and void* are the same.
This could be a sneaky bug, and should encourage always declaring
NULL as (void*)0.

s/declaring/defining/, I would think is the correct terminology.
Anyway...

This is a sneaky bug that *only* arises if the programmer passes
an unadorned 'NULL' to a variadic function expecting a pointer.
What's more, the bug is only *fixable* if that variadic function
expects a pointer to void (not any other kind of pointer). In
practice, I bet this boils down to "This sneaky bug appears only
when the programmer calls 'printf("%p", NULL)'", which obviously
never, ever happens except in toy pedagogical programs. Which
aren't really the focus of compiler implementors, I would think.

So, while IMHO it does make more sense to define NULL using the
"more strict" definition of (void*)0 rather than an unadorned 0,
I don't think the otherwise-undefined behavior of a pathological
and non-conforming program is a compelling argument for it. :)

-Arthur
 
P

Peter Slootweg

Arthur J. O'Dwyer said:
]
So it appears to me that if the system declares NULL as (void *)0
then the code:

printf("%p\n", NULL);

is well defined, but if the system simply declares NULL as 0 there
could be a problem. This is fairly obvious in this case, but may
not be if the pointer variable is passed in. The problem will
_probably_ not arise if the sizes of int and void* are the same.
This could be a sneaky bug, and should encourage always declaring
NULL as (void*)0.

s/declaring/defining/, I would think is the correct terminology.
Anyway...

This is a sneaky bug that *only* arises if the programmer passes
an unadorned 'NULL' to a variadic function expecting a pointer.
What's more, the bug is only *fixable* if that variadic function
expects a pointer to void (not any other kind of pointer). In
practice, I bet this boils down to "This sneaky bug appears only
when the programmer calls 'printf("%p", NULL)'", which obviously
never, ever happens except in toy pedagogical programs. Which
aren't really the focus of compiler implementors, I would think.

So, while IMHO it does make more sense to define NULL using the
"more strict" definition of (void*)0 rather than an unadorned 0,
I don't think the otherwise-undefined behavior of a pathological
and non-conforming program is a compelling argument for it. :)

-Arthur

what about the case where your variadic function expects NULL to terminate a
list of pointers to void

e.g. (not compiled)

size_t myfunc(void *p1,...)
{
size_t c;
va_list list;
va_start(list,p1);
c = 0;
while (p1 != NULL)
{
/* do something uninteresting with p1 - actually just count */
c++;
p1 = va_arg(list,void *);
}
return c;
}
 
D

Dan Pop

]
So it appears to me that if the system declares NULL as (void *)0
then the code:

printf("%p\n", NULL);

is well defined, but if the system simply declares NULL as 0 there
could be a problem. This is fairly obvious in this case, but may
not be if the pointer variable is passed in. The problem will
_probably_ not arise if the sizes of int and void* are the same.
This could be a sneaky bug, and should encourage always declaring
NULL as (void*)0.

s/declaring/defining/, I would think is the correct terminology.
Anyway...

This is a sneaky bug that *only* arises if the programmer passes
an unadorned 'NULL' to a variadic function expecting a pointer.
What's more, the bug is only *fixable* if that variadic function
expects a pointer to void (not any other kind of pointer). In
practice, I bet this boils down to "This sneaky bug appears only
when the programmer calls 'printf("%p", NULL)'", which obviously
never, ever happens except in toy pedagogical programs. Which
aren't really the focus of compiler implementors, I would think.

So, while IMHO it does make more sense to define NULL using the
"more strict" definition of (void*)0 rather than an unadorned 0,
I don't think the otherwise-undefined behavior of a pathological
and non-conforming program is a compelling argument for it. :)

A much more compelling argument for NULL as (void*)0 is the frequently
seen assumption that NULL can be used anywhere a plain 0 does the job,
e.g. the null character terminating a string (the NULL name is probably
at the root of this confusion).

Dan
 
D

Dan Pop

Arthur J. O'Dwyer said:
]
So it appears to me that if the system declares NULL as (void *)0
then the code:

printf("%p\n", NULL);

is well defined, but if the system simply declares NULL as 0 there
could be a problem. This is fairly obvious in this case, but may
not be if the pointer variable is passed in. The problem will
_probably_ not arise if the sizes of int and void* are the same.
This could be a sneaky bug, and should encourage always declaring
NULL as (void*)0.

s/declaring/defining/, I would think is the correct terminology.
Anyway...

This is a sneaky bug that *only* arises if the programmer passes
an unadorned 'NULL' to a variadic function expecting a pointer.
What's more, the bug is only *fixable* if that variadic function
expects a pointer to void (not any other kind of pointer). In
practice, I bet this boils down to "This sneaky bug appears only
when the programmer calls 'printf("%p", NULL)'", which obviously
never, ever happens except in toy pedagogical programs. Which
aren't really the focus of compiler implementors, I would think.

So, while IMHO it does make more sense to define NULL using the
"more strict" definition of (void*)0 rather than an unadorned 0,
I don't think the otherwise-undefined behavior of a pathological
and non-conforming program is a compelling argument for it. :)
what about the case where your variadic function expects NULL to terminate a
list of pointers to void

Use the obvious: (void *)NULL or (void *)0. Anything not equivalent to
one of these forms (or a null void pointer) is a bug.

Dan
 
O

Old Wolf

CBFalconer said:
So it appears to me that if the system declares NULL as (void *)0
then the code:

printf("%p\n", NULL);

is well defined, but if the system simply declares NULL as 0 there
could be a problem. This is fairly obvious in this case, but may
not be if the pointer variable is passed in. The problem will
_probably_ not arise if the sizes of int and void* are the same.

It would arise if 0 and (void *)0 have different representations
(which is not that uncommon)
This could be a sneaky bug, and should encourage always declaring
NULL as (void*)0.

There are still problem situations, because (void *) could be a different
size to other pointer types, eg:

void put_strings(char *f1, ...)
{
va_list ap;
va_start(ap, f1);
while (f1 != NULL)
{
puts(f1);
f1 = va_arg(ap, char *);
}
va_end(ap);
}

If called with: put_strings("foo", NULL); would cause UB even if
NULL is (void *)0.
 
S

Sam Dennis

Old said:
void put_strings(char *f1, ...)

If called with: put_strings("foo", NULL); would cause UB even if
NULL is (void *)0.

Not in this case (pointers to void and `a character type' (normally
read as `all the character types') have identical representations),
but it's certainly a concern for pointers to other types, functions
in particular.
 
D

Dan Pop

In said:
Not in this case (pointers to void and `a character type' (normally
read as `all the character types') have identical representations),

Identical representation means exactly zilch in context. What you
need is identical argument passing mechanisms and the standard provides
no normative guarantees about that.

Dan
 
S

Sam Dennis

Dan said:
What you need is identical argument passing mechanisms and the
standard provides no normative guarantees about that.

That's probably true, but I doubt that there's any conforming
implementation in existence that does not follow the footnote
strongly suggesting this reading.
 
D

Dan Pop

In said:
That's probably true, but I doubt that there's any conforming
implementation in existence that does not follow the footnote
strongly suggesting this reading.

Are they still maintaining the DS9k?

Dan
 
D

Dave Thompson

In said:
Old said:
void put_strings(char *f1, ...)
[ and using va_arg to get as char* ]
If called with: put_strings("foo", NULL); would cause UB even if
NULL is (void *)0.

Not in this case (pointers to void and `a character type' (normally
read as `all the character types') have identical representations),

Identical representation means exactly zilch in context. What you
need is identical argument passing mechanisms and the standard provides
no normative guarantees about that.
It does in C99: 7.5.1.1p2 for va_arg, and 6.5.2.2p6 for (wholly)
unprototyped/K&R1 definitions. Also for (the shared range of)
corresponding signed and unsigned integers. Presumably if any
implementor wasn't already doing these and wasn't willing to change
they would have objected.

- David.Thompson1 at worldnet.att.net
 
D

Dan Pop

In said:
In said:
Old Wolf wrote:
void put_strings(char *f1, ...)
[ and using va_arg to get as char* ]
If called with: put_strings("foo", NULL); would cause UB even if
NULL is (void *)0.

Not in this case (pointers to void and `a character type' (normally
read as `all the character types') have identical representations),

Identical representation means exactly zilch in context. What you
need is identical argument passing mechanisms and the standard provides
no normative guarantees about that.
It does in C99: 7.5.1.1p2 for va_arg, and 6.5.2.2p6 for (wholly)
unprototyped/K&R1 definitions. Also for (the shared range of)
corresponding signed and unsigned integers. Presumably if any
implementor wasn't already doing these and wasn't willing to change
they would have objected.

This is correct, if you fix the va_arg reference. I had the printf case
in mind, where 7.15.1.1p2 doesn't apply.

Dan
 

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

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,149
Latest member
Vinay Kumar Nevatia0
Top