two meanings of a cast

F

Felix Kater

I haven't been thinking about it for years but recently I've stumbled on
the fact that 'casting' is actually doing (at least) two different
things:

On the one hand 'casting' means: 'Change something into somthing
else'. Example: double d=9.99; long l=(double)d;

On the other hand 'casting' means: 'Treating something as something
else without change'. For instance when casting function pointers.

Would You agree?

IMO this difference between 'conversion' and 'treatment' isn't probably
pointed out enough to C newcomers and may be one reason for the danger
of casts.

Felix
 
C

Chris Dollin

Felix said:
I haven't been thinking about it for years but recently I've stumbled on
the fact that 'casting' is actually doing (at least) two different
things:

On the one hand 'casting' means: 'Change something into somthing
else'. Example: double d=9.99; long l=(double)d;

On the other hand 'casting' means: 'Treating something as something
else without change'. For instance when casting function pointers.

Would You agree?

No.

In a cast expression `(T) E`, we're requiring the value of the expression
`E` to be converted to type `T` using the appropriate conversion machinery.

That's true everywhere.

Sometimes, the "appropriate conversion machinery" leaves the underlying
bit-pattern unchanged, leading to the illusion that the cast means "Treat
something as something else without change". But this is an accident of
implementation.

[We have seen in the past that conversion from say `char*` to `int*` and
back requires bit-fiddling code in some implementations.]
IMO this difference between 'conversion' and 'treatment' isn't probably
pointed out enough to C newcomers and may be one reason for the danger
of casts.

I don't think it's as simple as that. I suspect the danger comes from
casts of pointers and a belief that if `p` points somewhere sane then
the pointer `(T*) p` points somewhere equally sane and is dereferencable
to get values of type `T`.
 
C

Charlton Wilbur

FK> On the one hand 'casting' means: 'Change something into
FK> somthing else'. Example: double d=9.99; long l=(double)d;

FK> On the other hand 'casting' means: 'Treating something as
FK> something else without change'. For instance when casting
FK> function pointers.

FK> Would You agree?

No; in your first case, you're also "treating something as something
else without change." The value of d does not change when you cast it
to a double (completely unnecessary, since that's what it is) and
assign it to l.

Charlton
 
P

pemo

Sometimes, the "appropriate conversion machinery" leaves the underlying
bit-pattern unchanged, leading to the illusion that the cast means "Treat
something as something else without change". But this is an accident of
implementation.

<snip>

Are you saying here that there exists an implementation that would
alter the bit pattern of an object as it is cast [keeping the
original's size constant] - or that the change in bits would likely be
the result of changing the size of the original?
 
E

Eric Sosman

Felix said:
I haven't been thinking about it for years but recently I've stumbled on
the fact that 'casting' is actually doing (at least) two different
things:

On the one hand 'casting' means: 'Change something into somthing
else'. Example: double d=9.99; long l=(double)d;

That's fine, although it's a silly cast. `d' is already
a `double', so "converting" it to a `double' is unnecessary.
(The `double' result, whether "converted" or not, will then
be converted to `long' for initializing `l'.)
On the other hand 'casting' means: 'Treating something as something
else without change'. For instance when casting function pointers.

Would You agree?

No. A cast is an operator that performs a conversion (even
if the conversion is vacuous, as above). There is no such thing
as a "let's pretend" cast.

However, casts can convert a pointer of one type into a pointer
to another type, and there is something like a "let's pretend"
effect if the converted result is then used to access a pointed-
to object or function:

int i = 42;
unsigned char *p = (unsigned char*) &i;
printf ("The individual bytes of %d are:", i);
while (p < (unsigned char*)(&i + 1))
printf (" %d", *p++);
printf ("\n");

Line two takes the pointer-to-int value `&i' and converts
it to a pointer-to-unsigned-char, which it then stores in `p'.
There is no "let's pretend" at all: A value is converted from one
type to another, and that's that.

There's another such conversion on line four, where another
pointer-to-int is converted to pointer-to-unsigned-char for
purposes of comparison. Again, there's no "let's pretend:" the
cast converts the value `(&i + 1)' to a different type.

The "let's pretend" occurs on line five, where a pointer
to one type (unsigned char) is used to manipulate an object of
a different type (int). Observe that there is no cast in line
five: We are "treating something as something else without
change" by accessing it with different kinds of pointers, not
by applying a cast to it. (No cast is ever applied to `i'.)
IMO this difference between 'conversion' and 'treatment' isn't probably
pointed out enough to C newcomers and may be one reason for the danger
of casts.

It would be a disservice to newcomers to point out a difference
that doesn't exist. For the same reason, most introductory texts
omit mentioning the temperatures of C's data types.
 
F

Felix Kater

Sometimes, the "appropriate conversion machinery" leaves the
underlying bit-pattern unchanged, leading to the illusion that the
cast means "Treat something as something else without change". But
this is an accident of implementation.

Some notes:

'sometimes': This is exactly what I mean--from a students view. Talking
about casts does not give you the clue if data is converted or not.

'illusion': In practice it *is* a big difference if you can or cannot
cast something into somthing else and back again without having changed
a single byte. So calling it an illusion wouldn't help.

'accident of implementation': I wonder if there aren't cases in which
legal casts could simply never be 'conversions' nor make sense
as such in any implementation. So, this was reason why casts are ment
not to be conversions sometimes.

Felix
 
F

Felix Kater

Example: double d=9.99; long l=(double)d;

'd' is already a `double', so "converting" it to a `double' is
unnecessary.

True, that was a silly mistake: I meant:
Example: double d=9.99; long l=(long)d;

Felix
 
A

Army1987

Are you saying here that there exists an implementation that would
alter the bit pattern of an object as it is cast [keeping the
original's size constant] - or that the change in bits would likely be
the result of changing the size of the original?

If sizeof(int) equals sizeof(float), do you expect

int i = SOME_INTEGER_CONSTANT;
float f = (float)i;

!memcmp(&i, &f, sizeof i)

to always be true, on *any* implementation?
 
C

Chris Dollin

pemo said:
Sometimes, the "appropriate conversion machinery" leaves the underlying
bit-pattern unchanged, leading to the illusion that the cast means "Treat
something as something else without change". But this is an accident of
implementation.

Are you saying here that there exists an implementation that would
alter the bit pattern of an object as it is cast [keeping the
original's size constant] - or that the change in bits would likely be
the result of changing the size of the original?

No, I'm saying that sometimes the conversion doesn't alter the bitpattern,
so looking at the bitpattern gives the illusion that the cast doesn't
really do anything.

(EG one could play with `unsigned char *` to discover the values of the
bytes and that the bytes of the converted value were equal to the bytes
of the original value; or one could read the assembler output of a
I-can-do-assembler-output compiler.)

This will happen with eg casting ints to longs and vice-versa on machines
where they're the same size, and often happens with casting between
object pointer values.
 
C

Chris Dollin

Felix said:
Some notes:

'sometimes': This is exactly what I mean--from a students view. Talking
about casts does not give you the clue if data is converted or not.

Excuse me, it does: the data is /always/ converted.

Sometimes the implementation of the conversion is trivial and preserves
the bit-pattern. Doesn't matter: the value has been converted.
'illusion': In practice it *is* a big difference if you can or cannot
cast something into somthing else and back again without having changed
a single byte.

Some conversions are lossless; some are not. Just because /sometimes/
the implementation doesn't have to do anything doesn't mean it always
won't.

If you stick to what the Standard guarantees, you won't write code
where your round-tripping is surprising.

I'd like examples of your "In practice it *is* a big difference".
So calling it an illusion wouldn't help.

'accident of implementation': I wonder if there aren't cases in which
legal casts could simply never be 'conversions' nor make sense
as such in any implementation. So, this was reason why casts are ment
not to be conversions sometimes.

It's not the reason, because they're not sometimes-not-conversions.
 
E

Eric Sosman

Felix said:
Some notes:

'sometimes': This is exactly what I mean--from a students view. Talking
about casts does not give you the clue if data is converted or not.

Yes, it does: A cast always, as in always, converts its
operand. Sometimes the conversion is vacuous, as in the
example you posted earlier of applying `(double)' to a `double'
value. It is a conversion nonetheless, just as `+' adds two
numbers even when one of them is zero. (Note that the bits
of the result of a vacuous cast need not agree with the bits
of the operand value: for example, double-to-double might
deliver the normalized equivalent of an unnormalized operand
on machines that support unnormalized floating-point.)

A cast is a unary operator, like unary `-' or unary `&'.
It delivers a result derived from its single operand; that
result may have a different type, and the conversion to a new
type may also produce a different value. The operand itself
is "read-only," and is never[*] changed by the cast.

[*] Well, if the operand is a `volatile' object, its value
may change as a result of being read. But that's not an effect
of the cast; it's the volatility that's at work.
'illusion': In practice it *is* a big difference if you can or cannot
cast something into somthing else and back again without having changed
a single byte. So calling it an illusion wouldn't help.

Some conversions are guaranteed to be reversible, some
are not. Some are guaranteed to be reversible only under
restricted circumstances (e.g., a `double' can be converted
to an `int' and then back again unchanged only if it's in a
suitable range and has no fractional part). Some are not
permitted at all. What's the big deal?
'accident of implementation': I wonder if there aren't cases in which
legal casts could simply never be 'conversions' nor make sense
as such in any implementation. So, this was reason why casts are ment
not to be conversions sometimes.

A cast is always a conversion, even if it merely converts
an operand's value to the operand's own type.
 
¬

¬a\\/b

int i = 42;
unsigned char *p = (unsigned char*) &i;
printf ("The individual bytes of %d are:", i);
while (p < (unsigned char*)(&i + 1))
printf (" %d", *p++);
printf ("\n");
(for me the above should print what is in *(int*)(p+1))
i don't like to talk but is it not better
printf (" %d", (int) *p++);
?
 
E

Eric Sosman

¬a\/b wrote On 04/27/07 15:27,:
On Fri, 27 Apr 2007 08:58:24 -0400, Eric Sosman


(for me the above should print what is in *(int*)(p+1))

No, not at all. Are you sure what you wrote is
what you meant?
i don't like to talk but is it not better
printf (" %d", (int) *p++);
?

Yes, that would be slightly better. On most machines,
`unsigned char' promotes to `int', but on "exotic" machines
it could promote to `unsigned char'. (Such a machine would
have UCHAR_MAX > INT_MAX, hence sizeof(int)==1 and a `char'
at least sixteen bits wide. I'm told such machines exist.)
If it promotes to `unsigned int', then "%d" is the wrong
conversion specifier; "%d" is for plain `int'.

Still better would be

printf(" %u", (unsigned int) *p++);

... because it works correctly even when `p' points to
a value that is too large for an `int'. That's not a
problem in my example, but not all numbers are forty-two!
 
¬

¬a\\/b

¬a\/b wrote On 04/27/07 15:27,:

No, not at all. Are you sure what you wrote is
what you meant?

in the time i were sure now not;
it seems the compiler here cast the argument of printf
to int first than to call printf. is that behaviour for every function
or just for printf? is this behaviour just for chars or is for all
types?
Yes, that would be slightly better. On most machines,
`unsigned char' promotes to `int', but on "exotic" machines
it could promote to `unsigned char'. (Such a machine would

in a machine like above should be
sizeof(void*)<=sizeof(unsigned char)
in the other case how is it possible "to printf" a pointer
if sizeof(char*)> sizeof(unsigned char) and cast the pointer
with (unsigned char)
have UCHAR_MAX > INT_MAX, hence sizeof(int)==1 and a `char'
at least sixteen bits wide. I'm told such machines exist.)
If it promotes to `unsigned int', then "%d" is the wrong
conversion specifier; "%d" is for plain `int'.

Still better would be

printf(" %u", (unsigned int) *p++);

... because it works correctly even when `p' points to
a value that is too large for an `int'. That's not a
problem in my example, but not all numbers are forty-two!

this is more than i can understand
 
E

Eric Sosman

¬a\/b said:
in the time i were sure now not;
it seems the compiler here cast the argument of printf
to int first than to call printf. is that behaviour for every function
or just for printf? is this behaviour just for chars or is for all
types?

When you pass an argument and the compiler does not know
the type of the corresponding parameter (that is, when you are
calling a function with no prototype or when you are passing
one of the "..." arguments to a variable-argument function),
the compiler "promotes" the argument by converting its value
to one of a standard set of types. For example, arguments of
type float are converted to double and passed to the function
as if you had provided double values to begin with.

"Narrow" integer types -- char, short, some others -- are
also promoted, but the promotion is a little bit tricky:

- If all possible values of the narrow type are within the
range of int, the value is promoted to an int and passed
as if you had provided an int argument.

- Otherwise, the value is promoted to an unsigned int and
passed as if you had provided an unsigned int argument.

So: *p is an unsigned char, which is a "narrow" integer
type. On most machines int has a wider range than char, so the
value of *p will be converted from char to int and passed to
printf() as an int. On a few unusual machines it is possible
that unsigned char can hold values that are numerically larger
than an int can hold, and on these machines *p will be converted
to unsigned int. (On these machines you will have sizeof(int)==1,
and since int is at least sixteen bits wide it follows that char
will be at least sixteen bits wide.)
in a machine like above should be
sizeof(void*)<=sizeof(unsigned char)

Since sizeof(unsigned char) is 1, `=' is possible but
`<' is not.
in the other case how is it possible "to printf" a pointer
if sizeof(char*)> sizeof(unsigned char) and cast the pointer
with (unsigned char)

I'm sorry: I do not understand your question.
this is more than i can understand

As explained above, there is some ambiguity about the
type of the value printf() will receive when you provide an
unsigned char argument: On most machines the argument will be
converted to int, but on unusual machines it may be converted
to unsigned int. This line removes the ambiguity by casting
the value to unsigned int: now printf() will receive an unsigned
int on all machines, typical and unusual. But "%d" is not the
right conversion specifier for unsigned int; "%d" works with an
ordinary int or signed int value. Since the value in this case
will be an unsigned int, I use "%u" instead of "%d".
 
O

Old Wolf

I haven't been thinking about it for years but recently I've stumbled on
the fact that 'casting' is actually doing (at least) two different
things:

A little off-topic here, but in C++ they introduced new syntax to
distinguish between three different 'sorts' of casting from C:
- static_cast is for conversions of values
- reinterpret_cast is for converting a pointer to be one that
points to a different type
- const_cast is for adding or removing const or volatile
qualifiers without changing anything else.
 
W

Wade Ward

Old Wolf said:
A little off-topic here, but in C++ they introduced new syntax to
distinguish between three different 'sorts' of casting from C:
- static_cast is for conversions of values
- reinterpret_cast is for converting a pointer to be one that
points to a different type
- const_cast is for adding or removing const or volatile
qualifiers without changing anything else.
Good to know.
 
A

Army1987

No, I'm saying that sometimes the conversion doesn't alter the bitpattern,
so looking at the bitpattern gives the illusion that the cast doesn't
really do anything.

(EG one could play with `unsigned char *` to discover the values of the
bytes and that the bytes of the converted value were equal to the bytes
of the original value;
Or just use memcmp()...;
 

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,768
Messages
2,569,574
Members
45,051
Latest member
CarleyMcCr

Latest Threads

Top