Type-checking casts for GNU C

C

c prog fan

jameskuyper said:
Keep in mind that the C and C++ standards don't distinguish between
compile time and run-time; they only talk about translation and
execution of a program. A conforming implementation could produce an
executable file that contains a copy of the source code files, and
doesn't actually perform any of the translation phases on those files
until just before each execution of the program.

Please correct these aloud thinkings if they are to be :

Well I have used my mind like an embedded device; what do you expect
from me then ? :)
I think this distinction is an excellent point to consider the
difference; for instance an implementation might leave some part of OS
related stuff to cope with when the program executes (char set comes to
mind); obviously those parts should not be (very) resource consuming
from a practical point of view.
If the compiler is able to predict with a certainty what the argument
of a given cast operator will be, before the program has even started
executing, it may be able to evaluate it during translation. In
general, this requires that the argument of the cast be a constant
expression, but a sufficiently smart compiler can look at an
algorithm, pre-determine what the result of applying that algorithm
will be, and optimize the program by just generating code for that
result, without actually generating any code for executing the
algorithm.

Sure this may happen but mainly I wanted to point that from a code
providing perspective it is not unlikely that this may not happen so one
has to put this into consideration. Say expecting worst case behavior.
Some casts are allowed inside of what C calls "integer constant
expressions", and what C++ calls "integral constant expressions" (I'll
just call them ICE's). An ICE that occurs in a context where an ICE is
required (contexts such as array lengths, case expressions, etc.) must
be evaluated during translation.

This is again interesting for me. The naming indicates that the is a
meaningful difference between the two. Thinking furthur I conclude that
C++ ICE might include C ICE as well as other types which are used for
static typing. This is not a suprize anyway because of templates of C++
and user defined types of it ( at least as I have heard of ).
Casts involving pointers and references cannot be evaluated until the
things those pointers and references refer to have been given actual
memory locations.
[...]

Can you explain a bit why this is not possible ? (Obviously I fail to
understand this ...)
What can occur during translation, with either language, is detection
of the fact that a given use of cast operator may make issuance of a
diagnostic message by the compiler mandatory. The difference between
the two languages is that diagnostics are mandatory for the named C++
casts under a much wider variety of circumstances than C casts. No
macro wrapper for a C cast can simulate that feature of C++.

Indeed one of my worries was this : Either C does the actual
verification during run time which as I guess might use extra resources
which in turn could be used by the actual program or it might abandon
some of checks which renders the program in danger (not much surprising
for me though). Of course a solution by the help of auxiliary
tools,libraries,... but that is not standard routine anyway.

And at last (but not least as some say) thanks for the rich content.
 
J

James Kuyper

c said:
jameskuyper wrote: ....

This is again interesting for me. The naming indicates that the is a
meaningful difference between the two. ...

There are differences in the meaning, but the difference in the names is
no clue to that difference. At some point someone decided that most of
the occurrences of the word "integral" in the C standard should be
replaced with "integer" - no corresponding change in meaning was
intended. No such change was made in the C++ standard. That's 100% of
the significance of that difference.

However, the C++ rules for ICEs allow some things to qualify that
wouldn't qualify as a C ICE; in particular, C++ allows variables with
static storage duration internal linkage that are initialized with an
ICE to themselves be treated as ICEs.
Casts involving pointers and references cannot be evaluated until the
things those pointers and references refer to have been given actual
memory locations.
[...]

Can you explain a bit why this is not possible ? (Obviously I fail to
understand this ...)

A cast acting on a pointer value takes the address of the thing that the
pointer value points at, and creates a value of new type based upon what
that address was. Therefore, it can't be evaluated until that address
has been determined. On many systems, that address is not determined
until run-time. On other systems, the addresses of objects with static
storage duration can be determined at link time, but even on those
systems, the addresses of objects with automatic or allocated storage
duration can only be determined at run time. I don't know of any way
that a address can be determined at compile time, except for hardware
addresses that have a fixed value regardless of the program that is
running. In C, such addresses would typically be created by converting
an integer constant into a pointer type, and inherently unportable
construct.
Indeed one of my worries was this : Either C does the actual
verification during run time which as I guess might use extra resources
which in turn could be used by the actual program or it might abandon
some of checks which renders the program in danger (not much surprising
for me though).

C casts don't do any verification checks. It's not a matter of using
extra resources, it's not a matter of "abandoning" checks; there are no
checks to be abandoned, and there's no resources that could be expended
to perform them. Your comments about this are so bizarre that I strongly
suspect that you have a seriously incorrect mental model of what a cast
does. However, I haven't figured out yet what's incorrect about it.

dynamic_cast<> is the only C++ cast that verifies it's argument. When
diagnostics are called for any of the other C++ casts that would not be
required for a C-style cast, the reason is always based upon the
relationship between the type of the of the value being converted and
the type that the value is being converted to; it has nothing to do with
the value itself.
 
H

Harald van Dijk

Harald said:
Harald van =?UTF-8?b?RMSzaw==?= wrote: [...]
and doesn't allow conversions between struct S * and const struct S
*.

I had an alternate version that would work for structs as well:

#define CONST_CAST(T,expr) \
(sizeof(&**(T*) 0 == &*(expr)) ? (T) (expr) : (T) (expr))

The only difference between &*x and x (when both are valid) is that the
former is not an lvalue. Since lvalue-ness is not relevant here, this
means simply

#define CONST_CAST(T,expr) \
(sizeof(*(T*) 0 == (expr)) ? (T) (expr) : (T) (expr))

But as you noted, &*x means x only when *x is valid, that is, when x is
a pointer. I don't think *x is valid when x is a void*, but I may be
wrong.

*x is valid when x is a void. The only constraint on unary * is that its
operand must have pointer type. You cannot do anything with it but discard
it or take its address, though.
But why would it allow dereferencing of a void* (even when its "treat
void* as char* in many cases" extension is disabled)? If dereferencing
void* really is valid, then surely we could generate a diagnostic by
adding
to it as well:

(T)0 + 1

Yes, this is invalid and requires a diagnostic. Pointer addition is only
allowed for pointers to object types.
Annoyingly, unless I do -pedantic, gcc (4.1.2) happily accepts addition
to a void*, dereferencing of a void*, sizeof that dereferenced value,
and even comparison of it to another dereferenced void*. Doesn't seem to
be a way to generate a diagnostic for default gcc settings. -Wall
doesn't help either.

Is there a problem with -pedantic that makes you want to avoid it?

Anyway, you could try passing a void to a function. That should surely
cause a warning.
The use of the comma operator prevents it from being used in some
contexts, since it prevents it from being a constant expression ($6.6).
That was the point of using the more-tedious conditional operator. But
your change can be accommodated:

#define CONST_CAST(T,expr) \
((sizeof *(T) 0) | \
(sizeof *(expr)) | \
(sizeof((T) 0 == (expr))) ? \
(T) (expr) : (T) (expr))

Thanks, and here's an addition :

#define CONST_CAST(T,expr) \
((sizeof ((char(*)(char, ...)) 0) \
(0, *(T) 0, *(expr), \
(T) 0 == (expr))) \
? (T) (expr) : (T) (expr))
I see you eliminated the unnecessary dereferencing in *(T*) 0. Unlike
the other cast macro, T must be a pointer type in this one, so you just
did (T) 0. Nice.

I like the pattern that's emerged. Similar to how C++ templates can
impose restrictions on the types by using them in expressions that
aren't evaluated, we can set up multiple expressions that must hold for
T and expr. So here we assert that T is a pointer type and not a void*,
that expr is a pointer type, and that there is an implicit conversion
between the two in at least one direction.

And now, that T is a pointer to an object type or incomplete array, that
expr is a pointer to an object type or an incomplete array, and that there
is an implicit conversion.
(sorry about your name getting mangled in the quote attribution; my
newsreader is old, along with my OS)

Not a problem. And it doesn't actually get mangled by your newsreader;
your attribution lines match what's in my message's headers.
 
C

c prog fan

James said:
C casts don't do any verification checks. It's not a matter of using
extra resources, it's not a matter of "abandoning" checks; there are no
checks to be abandoned, and there's no resources that could be expended
to perform them. Your comments about this are so bizarre that I strongly
suspect that you have a seriously incorrect mental model of what a cast
does. However, I haven't figured out yet what's incorrect about it.

dynamic_cast<> is the only C++ cast that verifies it's argument. When
diagnostics are called for any of the other C++ casts that would not be
required for a C-style cast, the reason is always based upon the
relationship between the type of the of the value being converted and
the type that the value is being converted to; it has nothing to do with
the value itself.

Well I consider a (C style) cast as being a way of interpretation of
data (Obviously for those casts which won't face diagnostics). I mainly
wanted to ask about the differences of casts in C++ and C. Say What
happens when in C++ a cast needs diagnostics but C style won't, Are
there such cases ? If so I thought maybe C won't consider potential
dangers (if there is any) after all and one might expect undefined or
implementation defined behaviors.

Now please correct that if needed. Thanks.
 
J

James Kuyper

c said:
Well I consider a (C style) cast as being a way of interpretation of
data (Obviously for those casts which won't face diagnostics). I mainly
wanted to ask about the differences of casts in C++ and C. Say What
happens when in C++ a cast needs diagnostics but C style won't, Are
there such cases ?

Sure, quite a few. There's lots of ways the behavior of a C cast can be
undefined, but diagnostics are required only for syntax errors and
constraint violations. Arguably, you're not actually using a cast if you
don't get the syntax right, and there's only two constraints on C casts:

"2 Unless the type name specifies a void type, the type name shall
specify qualified or unqualified scalar type and the operand shall
have scalar type.
3 Conversions that involve pointers, other than where permitted by the
constraints of 6.5.16.1, shall be specified by means of an explicit
cast."

In C++, most syntactically valid uses of any of the named casts
constitute diagnosable errors. Some conversions occur implicitly, so
there's no need to use a cast to perform them. Most conversions can only
occur explicitly, and many (most? all?) of those conversions fall into
two categories:

Some of them can be performed by only one of the named casts; any
attempt to use a different named cast is a diagnosable error.

Some of them require a series of named casts; for each step in the
series, using a different named cast instead of the right one is a
diagnosable error.
... If so I thought maybe C won't consider potential
dangers (if there is any) after all and one might expect undefined or
implementation defined behaviors.

Any conversion that requires a cast is potentially dangerous; all of the
safe conversions occur implicitly. Any time you use a cast to explicitly
request a conversion, you're taking responsibility for determining
whether or not it's the correct thing to do, and relieving the compiler
of any responsibility for it. This is just as true in C as in C++. The
C++ named casts just break the risks up into smaller pieces.
 
K

Keith Thompson

c prog fan said:
Well I consider a (C style) cast as being a way of interpretation of
data (Obviously for those casts which won't face diagnostics).

No, a cast specifies a type conversion. In some cases, such a type
conversion might be implemented as a reinterpretation of the
representation of the value, but that's not what the conversion means.
I mainly
wanted to ask about the differences of casts in C++ and C. Say What
happens when in C++ a cast needs diagnostics but C style won't, Are
there such cases ? If so I thought maybe C won't consider potential
dangers (if there is any) after all and one might expect undefined or
implementation defined behaviors.

Questions about C++ casts would be more appropriate in a C++
newsgroup. But check the C++ FAQ first:
<http://www.parashift.com/c++-faq-lite/>.
 
C

c prog fan

James said:
Sure, quite a few. There's lots of ways the behavior of a C cast can be
undefined, but diagnostics are required only for syntax errors and
constraint violations. Arguably, you're not actually using a cast if you
don't get the syntax right, and there's only two constraints on C casts:

"2 Unless the type name specifies a void type, the type name shall
specify qualified or unqualified scalar type and the operand shall
have scalar type.
3 Conversions that involve pointers, other than where permitted by the
constraints of 6.5.16.1, shall be specified by means of an explicit
cast."

In C++, most syntactically valid uses of any of the named casts
constitute diagnosable errors. Some conversions occur implicitly, so
there's no need to use a cast to perform them. Most conversions can only
occur explicitly, and many (most? all?) of those conversions fall into
two categories:

Some of them can be performed by only one of the named casts; any
attempt to use a different named cast is a diagnosable error.

Some of them require a series of named casts; for each step in the
series, using a different named cast instead of the right one is a
diagnosable error.


Any conversion that requires a cast is potentially dangerous; all of the
safe conversions occur implicitly. Any time you use a cast to explicitly
request a conversion, you're taking responsibility for determining
whether or not it's the correct thing to do, and relieving the compiler
of any responsibility for it. This is just as true in C as in C++. The
C++ named casts just break the risks up into smaller pieces.

Thanks; As for my conclusion it appears to me that I should use casts
only when it is really required.
 
C

c prog fan

Keith said:
No, a cast specifies a type conversion. In some cases, such a type
conversion might be implemented as a reinterpretation of the
representation of the value, but that's not what the conversion means.
Yes of course for example when a unsigned int casted to signed int IMHO
it is very likely that a conversion might be required. I used the word
interpretation in a broader sense but maybe is not appreciate one.
Questions about C++ casts would be more appropriate in a C++
newsgroup. But check the C++ FAQ first:
<http://www.parashift.com/c++-faq-lite/>.
Well this is related to C in the sense that some effort is made in this
thread to simulate C++ casts in C code; I just wanted to see how much
the effort can be successful in covering various aspects of casts.
 
K

Keith Thompson

c prog fan said:
Yes of course for example when a unsigned int casted to signed int IMHO
it is very likely that a conversion might be required. I used the word
interpretation in a broader sense but maybe is not appreciate one.

Actually, a conversion from unsigned int to signed int is very likely
to involve a simple reinterpretation of the representation. For an
unsigned int value that's representable as a signed int (<= INT_MAX),
I think the representations are required to be the same. For an
unsigned int value greater than INT_MAX, the result of the conversion
is implementation-defined; most implementations simply reinterpret the
bits because that's the easiest thing to do.

A better example is a conversion between integer and floating-point
types; 42 and 42.0 almost certainly have different representations.
Well this is related to C in the sense that some effort is made in this
thread to simulate C++ casts in C code; I just wanted to see how much
the effort can be successful in covering various aspects of casts.

C++ provides C-style casts, with (as far as I know) the same semantics
they have in C. If you wanted to ask how to implement C++-style casts
(const_cast et al) using C-style casts *in C++*, that would be purely
a C++ question. (C++ is a strict superset of C, but it's close.)
 
C

c prog fan

Keith said:
C++ provides C-style casts, with (as far as I know) the same semantics
they have in C. If you wanted to ask how to implement C++-style casts
(const_cast et al) using C-style casts *in C++*, that would be purely
a C++ question. (C++ is a strict superset of C, but it's close.)

Indeed I assume you may want to replace the "*in C++*" with 'in C'.
Please note that Mr James Kuyper answered the particular case; see my
post which you replied to that first time in this thread and follow up
discussion on its way. Also there is some efforts on solving this by
blargg and others which sounds promising (see his post and follow ups);
though thats clearly above my level so maybe I should wait to get what's
happening there.
 
I

Ian Collins

Keith said:
C++ provides C-style casts, with (as far as I know) the same semantics
they have in C. If you wanted to ask how to implement C++-style casts
(const_cast et al) using C-style casts *in C++*, that would be purely
a C++ question. (C++ is a strict superset of C, but it's close.)
^
not
 
K

Keith Thompson

("is a strict superset" should be "is *not* a strict superset)
Indeed I assume you may want to replace the "*in C++*" with 'in C'.

No, I meant exactly what I wrote.
Please note that Mr James Kuyper answered the particular case; see my
post which you replied to that first time in this thread and follow up
discussion on its way. Also there is some efforts on solving this by
blargg and others which sounds promising (see his post and follow ups);
though thats clearly above my level so maybe I should wait to get what's
happening there.

Sure. I don't think your question is off-topic; you're asking how to
do certain things in C, and merely describing those things in terms of
C++ constructs. The problem is that answering your question requires
both knowledge of C and knowledge of C++. If you were to ask a
similar question in comp.lang.c++, it could be answered by people who
just know C++. They'd also have to know which features of C++ are
provided by C.

(BTW, I think the answer is that you can't fully emulate the behavior
of C++'s various cast operators in pure C -- that's why they were
added to C++.)
 
G

Guest

I'm not quite sure what "one style" you're referring to? C does indeed
have only one style, but you explicitly referred to C++ casts. C++ has
five styles (static_cast, const_cast, dynamic_cast, reinterpret_cast,
explicit cast (functional notation) and explicit cast (cast notation).
Each of those casts does have different features, different contexts
where they can be used, and different effects.

The cast-notation version can do almost everything that any of the
others can do, and a couple of additional things as well. It is
syntactically identical, and semantically quite similar, to the only
kind of cast that C has.

There's really nothing you can do with a macro like this to emulate in C
the distinctions that C++ makes; it must always come down to a
cast-notation cast in the end. Therefore, macros like this seem pretty
pointless to me unless they are intended to be used by code that is
expected to be compiled by both a C compiler and a C++ compiler.

they indicate intent and they can be grepped for
 
D

David Thompson

On Thu, 25 Dec 2008 05:11:44 GMT, James Kuyper
However, the C++ rules for ICEs allow some things to qualify that
wouldn't qualify as a C ICE; in particular, C++ allows variables with
static storage duration internal linkage that are initialized with an
ICE to themselves be treated as ICEs.
<OT>Nit: C++ allows variables (and static data members of classes)
- with integral(=integer) or enumeration type
- and with the const qualifier
- and initialized by an ICE.

It doesn't require static duration (except for static data members)
nor internal linkage. It does somewhat encourage them; in C++ a
variable declared at namespace scope that does not specify
storage-class=linkage defaults to internal if const and external if
not. This means the easiest way of writing 'named constants' does make
them static internal. The rough equivalent in C, a variable declared
at file scope, defaults to external regardless of const.

<ObTopic>A somewhat-FAQ here is
int foo (...parms...)
{
const int n = 4;
double x [n];
... code ...
}

which is:
- illegal in C<=95
- legal in C99 (or GNUC) because it's a VLA (but not an ICE)
- legal in C++(98) because it's an ICE (but not a VLA)
 

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

Similar Threads


Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,132
Latest member
TeresaWcq1
Top