Is this valid C statement?

D

Dan Pop

In said:
Where does the standard say that a conversion between a function
pointer and an object pointer, even with an explicit cast, is allowed?

Nowhere. I have explicitly stated, upthread, that this is a case of
undefined behaviour (reread the thread if your memory is leaky). So what
*exactly* is your point?

If you don't get my point, here it is: this is one case when invoking
undefined behaviour is not *necessarily* a bad thing, if you know what
you're doing.

And if you still don't get it, please point out a way of using dlsym()
without invoking undefined behaviour by converting its return value
to a pointer to function. The fact that dlsym() is not a standard
library function is immaterial: the <dlfcn.h> interface is covered by
Unix standards and there are Unix programs needing it.

Dan
 
B

boa

Keith Thompson wrote:

[snip]
Where does the standard say that a conversion between a function
pointer and an object pointer, even with an explicit cast, is allowed?
(C99 6.3.2.3 discusses conversions between a pointer to an object or
incomplete type and a pointer to a different object or incomplete
type, and conversions between different pointer-to-function types, but
there's no mention of conversions between pointer-to-object types and
pointer-to-function types.) Or is it implicitly allowed (with
undefined behavior) because the standard doesn't disallow it?

Doesn't 6.3.2.3#5 and #6 allow us to convert "Any pointer type" to and
from an integer? Ignoring the obvious implementation-defined issues, it
should therefore be OK to convert pfunc -> int -> pdata, shouldn't it?

boa


[snip]
 
K

Keith Thompson

Nowhere. I have explicitly stated, upthread, that this is a case of
undefined behaviour (reread the thread if your memory is leaky). So what
*exactly* is your point?

So if it's a case of undefined behavior, then it's "allowed", in the
sense in which I was using the word. My meaning was clear from the
last sentence of that paragraph that you snipped:

] Or is it implicitly allowed (with undefined behavior) because the
] standard doesn't disallow it?
If you don't get my point, here it is: this is one case when invoking
undefined behaviour is not *necessarily* a bad thing, if you know what
you're doing.

Yes, I understand that.
And if you still don't get it, please point out a way of using dlsym()
without invoking undefined behaviour by converting its return value
to a pointer to function. The fact that dlsym() is not a standard
library function is immaterial: the <dlfcn.h> interface is covered by
Unix standards and there are Unix programs needing it.

Yes, I understand that.

Here's the sample program I posted previously (with added comments):

int main(void)
{
double dbl;
int *obj_ptr;
int (*func_ptr)(int);

obj_ptr = (int*)func_ptr; /* (1) */
func_ptr = (int (*)(int))obj_ptr; /* (2) */

dbl = (double)obj_ptr; /* (3) */
obj_ptr = (int*)dbl; /* (4) */

return 0;
}

I believe that each of the 4 marked conversions either requires a
diagnostic or invokes undefined behavior. My suspicion is that (1)
and (2) invoke undefined behavior and do not require diagnostics, and
that (3) and (4) do require diagnostics. I haven't been able to
confirm this in the standard (though it's consistent with gcc's
behavior). C99 6.3.2.3 specifies what pointer conversions are
allowed; it doesn't mention either conversions between
pointer-to-function types and pointer-to-object types, or conversions
between pointer-to-object types and floating-point types. That seems
to me to imply that either all four conversions above require a
diagnostic, or that none of them do but all four invoke undefined
behavior. (This contradicts my suspicion, and implies that gcc is
misbehaving.)

Dan, I know you've asserted that converting between a function pointer
and an object pointer invokes undefined behavior. I don't necessarily
disagree; I'm just looking for a definitive statement, one way or the
other, from the standard. If you don't know the answer, or if you
still don't understand what I'm asking, please feel free to sit back
and let someone else answer my questions.
 
D

Dan Pop

In said:
So if it's a case of undefined behavior, then it's "allowed", in the
sense in which I was using the word.

Which is not the sense usually accepted in this newsgroup.
My meaning was clear from the
last sentence of that paragraph that you snipped:

] Or is it implicitly allowed (with undefined behavior) because the
] standard doesn't disallow it?

"Implicitly allowed (with undefined behavior)" is an oxymoron in c.l.c.
Note that NO compiler is required by the standard to accept this
programming construct.

A better choice of words from your part would have avoided this
misunderstanding.
Here's the sample program I posted previously (with added comments):

int main(void)
{
double dbl;
int *obj_ptr;
int (*func_ptr)(int);

obj_ptr = (int*)func_ptr; /* (1) */
func_ptr = (int (*)(int))obj_ptr; /* (2) */

dbl = (double)obj_ptr; /* (3) */
obj_ptr = (int*)dbl; /* (4) */

return 0;
}

I believe that each of the 4 marked conversions either requires a
diagnostic or invokes undefined behavior. My suspicion is that (1)
and (2) invoke undefined behavior and do not require diagnostics, and
that (3) and (4) do require diagnostics. I haven't been able to
confirm this in the standard (though it's consistent with gcc's
behavior). C99 6.3.2.3 specifies what pointer conversions are
allowed; it doesn't mention either conversions between
pointer-to-function types and pointer-to-object types, or conversions
between pointer-to-object types and floating-point types. That seems
to me to imply that either all four conversions above require a
diagnostic, or that none of them do but all four invoke undefined
behavior.

All four of them invoke undefined behaviour, by lack of definition of
behaviour:

2 If a ``shall'' or ``shall not'' requirement that appears outside
of a constraint is violated, the behavior is undefined. Undefined
behavior is otherwise indicated in this International Standard
by the words ``undefined behavior'' or by the omission of any
^^^^^^^^^^^^^^^^^^^^^^^^^
explicit definition of behavior. There is no difference in
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
emphasis among these three; they all describe ``behavior that
is undefined''.
(This contradicts my suspicion, and implies that gcc is misbehaving.)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Huh?!? If all of them invoke undefined behaviour, there is NO way gcc
could misbehave in handling them (regardless of what it actually does or
doesn't).

gcc associates meaningful (and possibly useful) semantics to the first
two cases, which is trivial on any von Neumann architecture and even
on Harvard architectures with data and code spaces of the same size.
So, it *silently* handles them (there is nothing requiring a diagnostic
for them in the standard).

OTOH, gcc doesn't assign any meaningful semantics to the last two cases.
Although not required to, he chooses to let the programmer know that,
from its point of view, they are meaningless, by issuing a diagnostic.

We're dealing here with two different reasons for the language
specification opting for undefined behaviour:

1. Some semantics could be specified, but it wouldn't make sense on all
C implementations. This is the case for conversions between data
pointers (pointers to incomplete types are still data pointers)
and function pointers. Compare your vanilla von Neuman architecture
with the AS/400 (128-bit data pointers and 768-bit function pointers).

2. No meaningful semantics could be defined for the operation. This is
the case for conversions betwee floating point values and pointer
values.
Dan, I know you've asserted that converting between a function pointer
and an object pointer invokes undefined behavior. I don't necessarily
disagree; I'm just looking for a definitive statement, one way or the
other, from the standard.

If the above is not definitive enough for you, you know where to find
comp.std.c. I don't claim to possess the ultimate interpretation of *any*
paragraph of the C standard...

Dan
 
K

Keith Thompson

Keith Thompson said:
Where does the standard say that a conversion between a function
pointer and an object pointer, even with an explicit cast, is allowed?
(C99 6.3.2.3 discusses conversions between a pointer to an object or
incomplete type and a pointer to a different object or incomplete
type, and conversions between different pointer-to-function types, but
there's no mention of conversions between pointer-to-object types and
pointer-to-function types.) Or is it implicitly allowed (with
undefined behavior) because the standard doesn't disallow it?
[...]

What I was really looking for was an explicit statement about which
conversions are legal and which are not, particularly those involving
pointer types. (By "legal", I mean not requiring a diagnostic.)

C99 6.3.2.3 enumerates a number of kinds of conversions that are
allowed (it uses the phrase "may be converted"). (6.3.1 discusses
arithmetic operands.) Some potential conversions are not enumerated,
for example conversions between pointer types and floating-point
types; such conversions are unlikely to make much sense on most
implementations, but it seemed odd to me that they aren't mentioned.
I was also wondering about even less sensible conversions, such as
between pointers and structs.

The piece of the puzzle that I was missing was 6.5.4, "Cast
operators":

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.

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.

I think this means that conversion between a pointer type and a
floating-point type does not require a diagnostic (it doesn't violate
any constraint), but it invokes undefined behavior, since the standard
doesn't define what the behavior is. On the other hand, conversion
between a pointer type and a struct type is illegal; an explicit cast
is not allowed because one of the types is not scalar, and there's no
way to do the conversion implicitly.

I've tried the following program with several C compilers:

int main(void)
{
double dbl = 0.0;
int *ptr;
ptr = (int*)dbl;
return 0;
}

The assignment, if I'm correct, is legal but invokes undefined
behavior. Every compiler I've tried prints a diagnostic and rejects
the program:

error: cannot convert to a pointer type

invalid cast expression

The indicated type conversion is invalid.

"double" cannot be converted to "int*".

In this statement, "dbl" is of type "double", and cannot be
converted to "pointer to int"

I suppose printing a diagnostic and rejecting the program is a valid
consequence of undefined behavior, but it's not something that most
compilers generally do. Most C compilers typically print at most a
warning for undefined behavior, but don't reject the program.

The behavior of any one compiler, or even of any five compilers, is
not of course definitive in interpreting the standard, but it does
appear that the implementers of those compilers thought that
converting a floating-point value to a pointer type is illegal.

Is there a statement in the C standard that a conversion from a
floating-point type to a pointer type is a constraint violation?

I acknowledge that this isn't a particularly practical question unless
you're writing a compiler; it wouldn't occur to most of us to try to
convert a floating-point value to a pointer type in the first place.
 
D

Dan Pop

In said:
The piece of the puzzle that I was missing was 6.5.4, "Cast
operators":

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.

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.

I think this means that conversion between a pointer type and a
floating-point type does not require a diagnostic (it doesn't violate
any constraint), but it invokes undefined behavior, since the standard
doesn't define what the behavior is.
Correct.

I've tried the following program with several C compilers:

int main(void)
{
double dbl = 0.0;
int *ptr;
ptr = (int*)dbl;
return 0;
}

The assignment, if I'm correct, is legal but invokes undefined
behavior.

The undefined behaviour is invoked *before* the assignment, by the time
the cast operator is evaluated. If the implementation happens to support
the conversion, the assignment itself *may* invoke undefined behaviour,
too, depending on the actual result of the conversion. But, as far as the
language specification is concerned, this is irrelevant: only the first
instance of undefined behaviour counts.
Every compiler I've tried prints a diagnostic and rejects
the program:

error: cannot convert to a pointer type

invalid cast expression

The indicated type conversion is invalid.

"double" cannot be converted to "int*".

In this statement, "dbl" is of type "double", and cannot be
converted to "pointer to int"

I suppose printing a diagnostic and rejecting the program is a valid
consequence of undefined behavior,

It's explicitly listed in the standard:

2 NOTE Possible undefined behavior ranges from ignoring the
situation completely with unpredictable results, to behaving
during translation or program execution in a documented manner
characteristic of the environment (with or without the issuance of
a diagnostic message), to terminating a translation or execution
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(with the issuance of a diagnostic message).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
but it's not something that most
compilers generally do. Most C compilers typically print at most a
warning for undefined behavior, but don't reject the program.

There are two kinds of undefined behaviour at translation time:

1. The ones the compiler can detect. In most such cases, compilers assign
semantics to them, so they have no reason to reject the program. They
trust the programmer to be happy with the semantics provided by the
compiler. They may diagnose them, but typically only if the user
invokes the compiler in "picky" mode.

2. The ones the compiler cannot (or doesn't even try to) detect.
Obviously, no special action can be expected from the compiler.

Your example is a rare exception from the first category: the compiler
can detect it, but it doesn't assign any special semantics to it. What
should it do?
Is there a statement in the C standard that a conversion from a
floating-point type to a pointer type is a constraint violation?

Not that I know of. Why should there be one?

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

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,142
Latest member
arinsharma
Top