Questions, please

J

Joona I Palaste

Yes. I'm not convinced by that argument, however. Otherwise, surely instead
of:
printf("%f\n", d);
they'd write:
(void)(int)((int(*)(const char *, ...))printf((const char *)"%f\n",
(double)d));
Yes, you could argue that that's self-documenting, but it's hardly clearer,
is it?

Hmm, interesting. Try this for a program to calculate the sum of ten
numbers:

#include <stdio.h>
int main(void) {
int a[(int)10];
int i;
int sum=(int)0;
for ((int)(i=(int)0); (int)((int)i<(int)10); (int)i++) {
(void)(int)(((int *)a)[(int)i]=(int)i);
}
for ((int)(i=(int)0); (int)((int)i<(int)10); (int)i++) {
(void)(int)(sum=(int)((int)sum+(int)((int *)a)[(int)i])));
}
(void)(int)((int(*)(const char *, ...))printf((const char *)
"The sum is %d\n", (int)sum));
return (int)0;
}
 
M

Malcolm

Jack Klein said:
Um, err, cough, all right out with it...

RUBBISH! BALDERDASH!

Consider our old friend the signed magnitude integer machine, with 16
bit ints (INT_MAX = 32767, UINT_MAX = 65534). On such a platform, the
humble value of -1 is represented as (binary):

1000000000000001 (or 0x8001, if you prefer)

And yet, should such a value be cast (or even converted by assignment)
to unsigned int, the bits needs must rearrange themselves,
instantaneously, into:

1111111111111111 (known to friends as 0xFFFF)
You don't understand how to use natural language. NL statements are
generalisations, and failure to hold in some specialised circumstances
doesn't make them untrue.

eg Mammals are viviparous, true or false? The right answer is not "false"
(because of the montremes), "true" is acceptable, but not strictly correct.

Actually your example illustrates my point. x = (unsigned int) -1 should
ideally generate an error. In C it doesn't, probably to avoid the necessity
of introducing a test and conditional jump. It produces the bit pattern all
ones because that is a simple reinterpretation of bits on two's complement
machines, which are the vast majority in use. This leaves us with the
problem of what to do on one's complement machines - since they are not so
important they are forced to be an exception.
 
E

Eric Sosman

Malcolm said:
Actually your example illustrates my point. x = (unsigned int) -1 should
ideally generate an error.

Why?
In C it doesn't, probably to avoid the necessity
of introducing a test and conditional jump.

More probably, because the conversion from signed to
unsigned integers is well-defined by the Standard, and is
not an error of any kind. The compiler may complain about
the construct (it may complain about your Argyle socks if
it so chooses), but it may not reject the program if it's
free of other errors and exceeds no implementation limits.
There's no error in what you've shown, assuming `x' has a
reasonable definition (e.g., not `const FILE* x').
It produces the bit pattern all
ones because that is a simple reinterpretation of bits on two's complement
machines, which are the vast majority in use.

No: It produces the bit pattern of all ones because that
is the representation of UINT_MAX on all C-faring machines.
This leaves us with the
problem of what to do on one's complement machines - since they are not so
important they are forced to be an exception.

No: Even on a ones' complement or signed magnitude machine,
the result is the same. Of course, on these machines the cast
does more than the "reinterpretation of the bits" you mentioned
in an earlier post. WHY does the same value come out? Because
the conversion from signed int to unsigned int is defined in
terms of the *values* being converted, not of the bit patterns
that represent those values.
 
M

Malcolm

Eric Sosman said:
Because it is nonsense to try to express a negative number as an unsigned
integer.
More probably, because the conversion from signed to
unsigned integers is well-defined by the Standard, and is
not an error of any kind.
But the standard itself is a human construct. I would imagine that the main
reason it allows it is to avoid breaking hundreds of pre-standard programs.
The reason K and R allowed it was probably efficiency.
The compiler may complain about
the construct (it may complain about your Argyle socks if
it so chooses), but it may not reject the program if it's
free of other errors and exceeds no implementation limits.
There's no error in what you've shown, assuming `x' has a
reasonable definition (e.g., not `const FILE* x').
Obviously since I say the construct "should ideally generate an error" there
must be circumstances, non-ideal ones, in which the error is not generated.
No: It produces the bit pattern of all ones because that
is the representation of UINT_MAX on all C-faring machines.
So why does the standard specify that -1, of all things, must cast to
UINT_MAX?
No: Even on a ones' complement or signed magnitude machine,
the result is the same. Of course, on these machines the cast
does more than the "reinterpretation of the bits" you mentioned
in an earlier post. WHY does the same value come out? Because
the conversion from signed int to unsigned int is defined in
terms of the *values* being converted, not of the bit patterns
that represent those values.
You need to look at the motivation. Read what I said to Jack Klein about
natural language.
-1 casts to 0xFFFF because that is a reinterpretation of bits on two's
complement machines, and because the conversion can be accomplished in a
single machine instruction. Natural language needs to be socially
appropriate as well as literally accurate. In this case I am explaining C
casting to someone who doesn't understand very much about it, so mention of
one's complement machines, or the text of the standard, is not useful and
confuses.
 
M

Martin Dickopp

Malcolm said:
Because it is nonsense to try to express a negative number as an
unsigned integer.

Only if you think of `unsigned int' as a model for integer numbers,
which would be a pretty bad model, since there is an infinite number of
integers. It therefore makes sense to model integers modulo n instead.

Since it is a mathematical property of integers modulo n that -1 is
congruent to n-1, why shouldn't the same be true for `unsigned int'?
So why does the standard specify that -1, of all things, must cast to
UINT_MAX?

Probably because it is a sensible choice to base the properties of
`unsigned int' on integers modulo UINT_MAX+1. If you don't agree with
that, what other mathematical object do you think should `unsigned int'
be based on?

Martin
 
R

Régis Troadec

Hi,

Christopher Benson-Manica said:
Consider this post a friendly favor, then - teach is yet another
lovely irregular English verb, whose past tense is "taught". :)

Yes, I did a nice mistake. Thank you to correct me. My compiler is C89
full-compliant but my brain isn't english language full-compliant yet. :)

Regis
 
J

Jack Klein

^^^^^
UINT_MAX must be of the form pow(2,n)-1 where n is the number of value bits.
Therefore 65534 is impossible, it would still be 65535.

The thing with signed magnitude is that INT_MIN == -32767
instead of the usual INT_MIN == -32767-1

Thanks, my typo (or think-o).
 
C

CBFalconer

Eric said:
.... snip ...

A cast is an operator, just like `+' or `%'. Almost all[*]
C operators work with the values of their operand or operands,
and none[**] works with the representations.

And, IMO, a cast is another example of an overloaded C construct.
I consider that it would have been better to limit casts to
altering the 'typewise' interpretation of a bit pattern, provided
that the size of that pattern is the same for the original and the
cast type. After that there should specific functions to perform
the various transformations. However, that is not the situation.

Continuing the 'might have been speculation', we would then attach
different meanings to:

ch = (int)x; /* asserts sizeof x = sizeof int */
ch = int(x); /* transforms, preserving something */

However, 'tain't so, so nobody should assume such.
 
E

Eric Sosman

Malcolm said:
Because it is nonsense to try to express a negative number as an unsigned
integer.

That's not what the snippet attempts to do. It *converts*
a negative number to an `unsigned int', and the conversion obeys
the rules of modular arithmetic modulus `UINT_MAX+1'.

It is equally nonsensical to try to express a number with a
fractional part as an integer, but do you think `(int)(9.7 + 0.5)'
should be an error? Of course not: the *conversion* from `double'
to `int' is well-defined (for numbers in an appropriate range),
and is also useful. Should the compiler reject a useful and well-
defined operation as erroneous?
But the standard itself is a human construct. I would imagine that the main
reason it allows it is to avoid breaking hundreds of pre-standard programs.
The reason K and R allowed it was probably efficiency.

Perhaps. Have you asked them? Or "him," rather, because
K's role was to assist in describing R's invention. However,
until you can produce a statement from R to support your contention,
I'll continue to shave you with Occam's razor, and persist in my
outlandish supposition that the conversion is defined and not
erroneous because it is useful and well-behaved.
Obviously since I say the construct "should ideally generate an error" there
must be circumstances, non-ideal ones, in which the error is not generated.

This argument can, of course, justify or condemn absolutely
anything you like. As long as the arguer gets to control the
definition of "ideal," there's no externalizable content to the
debate. Solipsism rules -- and even the paranoid have enemies.
So why does the standard specify that -1, of all things, must cast to
UINT_MAX?

Look up "modular arithmetic" and "congruence."
You need to look at the motivation. Read what I said to Jack Klein about
natural language.

I read it, but I confess I didn't understand it. It seemed
entirely beside the point, a fog rather than an illumination. My
failing perhaps -- but when "everyone is out of step except
Johnny" it is reasonable to wonder about Johnny's sense of rhythm.
-1 casts to 0xFFFF because that is a reinterpretation of bits on two's
complement machines, and because the conversion can be accomplished in a
single machine instruction.

Already refuted, multiple times by multiple people. Also
self-contradictory: if the representation is already correct,
it should be "convertible" in *zero* machine instructions.
Natural language needs to be socially
appropriate as well as literally accurate. In this case I am explaining C
casting to someone who doesn't understand very much about it, so mention of
one's complement machines, or the text of the standard, is not useful and
confuses.

You are explaining your own mistaken understanding of C.
Your original statement was

"With the exception of casts from floating point types
to integers, C casts are simple reinterpretations of bits.
If the bit pattern doesn't make sense for the type you are
casting to, you will get garbage results and the compiler
won't warn about them."

.... and this is demonstrably (and demonstratedly) false. Perhaps
it would have been true if you had invented the language, and perhaps
you feel it "should" be true -- but it is not true, has never been
true, and (I'll bet) will never be true. When your "explanation"
of casting is flat-out wrong, you do a disservice by propounding it.
 
D

Dan Pop

In said:
Eric said:
... snip ...

A cast is an operator, just like `+' or `%'. Almost all[*]
C operators work with the values of their operand or operands,
and none[**] works with the representations.

And, IMO, a cast is another example of an overloaded C construct.
I consider that it would have been better to limit casts to
altering the 'typewise' interpretation of a bit pattern, provided
that the size of that pattern is the same for the original and the
cast type. After that there should specific functions to perform
the various transformations.

I don't get it. What's wrong with

double pi = 3.14;
(float)pi;

but would be OK with the hypothetical

float(pi);

???

I freely admit that I like the C++ way better, but I can't find anything
wrong with the C way of doing it. There is no fundamental difference
between (float)pi and float(pi). The latter merely saves a couple
of parentheses when a more complex expression is converted.

Dan
 
E

Eric Sosman

Dan said:
I don't get it. What's wrong with

double pi = 3.14;
(float)pi;

but would be OK with the hypothetical

float(pi);

???

I freely admit that I like the C++ way better, but I can't find anything
wrong with the C way of doing it. There is no fundamental difference
between (float)pi and float(pi). The latter merely saves a couple
of parentheses when a more complex expression is converted.

The syntax might be nasty when casting to more complicated
types:

(const struct something *)ptr

would presumably come out looking something like

const struct something *(ptr)

.... and the latter might be harder for compilers -- and humans --
to parse than the former.

Functionally, though, I'm with Dan: I don't see any
semantic difference between the proposal and current practice.
C needs to convert values from one type to another, and the
set of types is extensible by the programmer. Therefore, C
needs an extensible notation to express the conversions. One
might quarrel with the chosen form, but I think it would be
hard to quarrel with the underlying need.
 
C

CBFalconer

Dan said:
CBFalconer said:
Eric said:
... snip ...

A cast is an operator, just like `+' or `%'. Almost all[*]
C operators work with the values of their operand or operands,
and none[**] works with the representations.

And, IMO, a cast is another example of an overloaded C construct.
I consider that it would have been better to limit casts to
altering the 'typewise' interpretation of a bit pattern, provided
that the size of that pattern is the same for the original and the
cast type. After that there should specific functions to perform
the various transformations.

I don't get it. What's wrong with

double pi = 3.14;
(float)pi;

but would be OK with the hypothetical

float(pi);

Bear in mind that this is all blue-skying. I would like
"(float)pi;" to fail with an error message to the effect "float
and typeof pi are not the same size". However "float(pi);" would
perform the necessary transformatins. The first simply revises
the compilers view of the type involved, the second does real
modifications to the underlying representation.
 
E

Eric Sosman

CBFalconer said:
Bear in mind that this is all blue-skying. I would like
"(float)pi;" to fail with an error message to the effect "float
and typeof pi are not the same size". However "float(pi);" would
perform the necessary transformatins. The first simply revises
the compilers view of the type involved, the second does real
modifications to the underlying representation.

Sorry for misunderstanding the (blue-sky) proposal.
But why would anyone want the semantics you suggest? The
main drawback, I think is that they'd be utterly unportable:

- Since the implementation specifies the sizes of the
various types, `(float)pi' would compile on some
machines and toss errors on others.

- Since the implementation specifies the representations
of the various types, `(float)pi' would yield different
values on different systems where it happened to compile,
and it's even possible that some of these would be trap
representations.

Perhaps the construct might be of some use in sorting out
the three flavors of `char', but I'm having trouble coming up
with other situations where I'd want such a thing. Would you
mind posting a few descriptions of where such a beast would
be useful?

FWIW, a sort of "reinterpretation" can sometimes be had
by type-punning through pointers:

int kids_dont = 42;
float *try_this = (float*)&kids_dont;
float at_home = *try_this;

.... with all the well-known drawbacks.
 
B

Bubba

Sorry for misunderstanding the (blue-sky) proposal.
But why would anyone want the semantics you suggest? The
main drawback, I think is that they'd be utterly unportable:

Apparently the C++ standardization committee would want them. C++ has
static_cast and reinterpret_cast for this very thing, and, no, I
personally don't endorse them.
 
C

CBFalconer

Eric said:
Sorry for misunderstanding the (blue-sky) proposal.
But why would anyone want the semantics you suggest? The
main drawback, I think is that they'd be utterly unportable:

- Since the implementation specifies the sizes of the
various types, `(float)pi' would compile on some
machines and toss errors on others.

- Since the implementation specifies the representations
of the various types, `(float)pi' would yield different
values on different systems where it happened to compile,
and it's even possible that some of these would be trap
representations.

Is that bad? I would consider it to point out the
non-portability.
Perhaps the construct might be of some use in sorting out
the three flavors of `char', but I'm having trouble coming up
with other situations where I'd want such a thing. Would you
mind posting a few descriptions of where such a beast would
be useful?

FWIW, a sort of "reinterpretation" can sometimes be had
by type-punning through pointers:

int kids_dont = 42;
float *try_this = (float*)&kids_dont;
float at_home = *try_this;

... with all the well-known drawbacks.

Yes, but instead we find people using unions and wandering off
baffled and muttering to themselves. If a type name (or the
equivalent via a typedef) used as a function did the actual
conversion, the meaning of "float(kids_dont)" would, I think, be
clear to both the user and the compiler. The above "drawbacks"
would be signalled by an immediate error in "(float)kids_dont".
The above coding, with its drawbacks, would not be affected.

Since it will never happen it is not worth much discussion. I
brought it up primarily as an example of Cs overloading of
symbols.
 
P

Peter Nilsson

Richard Heathfield said:
Good question. :)

Your followup makes it clear that your choice of va_list wasn't random,

It was a very specific example of a 'useless' construct which you have
posted previously.
Yes. I'm not convinced by that argument, however. Otherwise, surely instead
of:
printf("%f\n", d);

they'd write:

(void)(int)((int(*)(const char *, ...))printf((const char *)"%f\n",
(double)d));

Yes, you could argue that that's self-documenting, but it's hardly clearer,
is it?

And surely (sic), all loops should finish with a continue statement?

This is really a sad argument as style practices are rarely taken to
their logical extreme.
But the cast isn't redundant - that is, code with it does not have the same
meaning as code without it.

How is the meaning of the code different? Most function calls are
context sensitive.
An explicit conversion is performed that
otherwise would not be performed.

In correct code it's the _same_ conversion.
That's right.


That's for the very obvious reason that there are advantages and
disadvantages to both styles.

How is malloc casting different? There are advantages and
disadvanteges to either method. It's a question of how we (as
individuals) value them.

Why?

I agree it's functional. But it has it's limitations and problems.
I can't think of a better one off-hand (although I wish it also applied to
function pointers, and that void ** had the same kinds of guarantees that
come with void *).

C++ isn't a better choice for purely generic programming? You think
the inherent problems of a type weak void * are _better_ than classes,
templates and overloaded function support?
 
R

Richard Heathfield

Peter said:
It was a very specific example of a 'useless' construct which you have
posted previously.

You follow these matters much more keenly than I, it seems. :) I don't
recall posting a va_list ap = {0} example myself.
And surely (sic), all loops should finish with a continue statement?

All /empty/ loops should finish with a continue statement. :)
This is really a sad argument as style practices are rarely taken to
their logical extreme.

If you find it a sad argument, I'm happy to end the discussion right here
(without prejudice on either side). I have no wish to sadden you, or indeed
anyone else.
How is the meaning of the code different?

With the cast, it contains an explicit conversion. That's what a cast /is/.
In correct code it's the _same_ conversion.

It's explicit, as opposed to implicit. I agree that the /outcome/ is the
same - in correct code. In incorrect code, however, it may not be.

How is malloc casting different? There are advantages and
disadvanteges to either method. It's a question of how we (as
individuals) value them.

advantages disadvantages

cast can hide a bug
none higher maintenance costs
more typing
obscures code

no cast doesn't hide bug
lower maintenance costs none
less typing
clear code

That's how I see it. Clearly, your mileage varies.

Because it gives me a decent amount of slack in the type system, which I can
use to good effect.
I agree it's functional. But it has it's limitations and problems.

What doesn't, in this life?

C++ isn't a better choice for purely generic programming?

I thought you were asking me about C, not C++. If you have a C++ question,
please ask in comp.lang.c++.
You think
the inherent problems of a type weak void * are _better_ than classes,
templates and overloaded function support?

If you want C++, you know where to find it.
 
S

Simon Biber

Joona I Palaste said:
Hmm, interesting. Try this for a program to calculate the
sum of ten numbers:

#include <stdio.h>
int main(void) {
int a[(int)10];
int i;
int sum=(int)0;
for ((int)(i=(int)0); (int)((int)i<(int)10); (int)i++) {

Perhaps the initialisation part of a for statement should
have an extra cast to (void) as it is evaluated as a void
statement? :)
(void)(int)(((int *)a)[(int)i]=(int)i);
}
for ((int)(i=(int)0); (int)((int)i<(int)10); (int)i++) {
(void)(int)(sum=(int)((int)sum+(int)((int *)a)[(int)i])));

There are one too many closing parentheses at the end of this line.
 
C

Chris Torek

You say 'statistically', have there been any formal studies on malloc
casting? If so, I'd love to read them. [Seriously!]

I do not know of any formal studies. Software engineering and/or
"human-computer interaction" folks are probably the people to ask
(although serious S.E. types might shudder at the thought of using
C at all :) ).
For me, sitting on the 'outside' observing the debates, I can't help but
think of the Stroustrup quote about C in general...

"C has its problems, ... [but] we know C's problems."

One of those problems is the use of unprototyped functions. Another is the
inherent weakness of type weak languages. C programmers have dealt
relatively comfortably with both for decades.

True enough. Something that still puzzles me now, though, is why
the C99 folks chose to allow non-prototype function declarations,
so that one can write:

void *malloc();

in one's own source code, without specifying the "size_t" parameter.
Requiring a diagnostic for non-prototype declarations would open
the door to removing the silly "(void)" syntax in "void f(void)"
in the next C standard.
 
D

Dave Thompson

CBFalconer said:
Bear in mind that this is all blue-skying. I would like
"(float)pi;" to fail with an error message to the effect "float
and typeof pi are not the same size". [.... or] simply revise[]
the compilers view of the type involved [....]
Like Ada UNCHECKED_CONVERSION or PL/I UNSPEC.
Apparently the C++ standardization committee would want them. C++ has
static_cast and reinterpret_cast for this very thing, and, no, I
personally don't endorse them.
No, C++ static_cast and reinterpret_cast, in spite of their names,
convert values possibly (and for some cases always) resulting in a
change of representation. There is no cast in C or C++ that is
portably guaranteed to just apply a different type to the same
representation -- although there are in both languages on any given
implementation sets of types which have the same representation, and
casting among *those* types keeps the representation (duh!).

- David.Thompson1 at worldnet.att.net
 

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
474,141
Messages
2,570,813
Members
47,357
Latest member
sitele8746

Latest Threads

Top