struct named 0

  • Thread starter Mohd Hanafiah Abdullah
  • Start date
J

Jack Klein

The "special handling" happens in cases like

int* p = 0; // Only legal because of special handling
if (p == 0)... // Only legal because of special handling

Both examples would be illegal if you replace 0 with 1. (double *) 1 is
perfectly legal, with implementation defined behavior.


The Standard does not have an explicit written rule for converting
integers of value 0 that are not null pointer constants, but the
"implementation defined behavior" cannot distinguish between a zero that
is a null pointer constant and a zero that is not a null pointer
constant. The conversion is _only_ based on the value. The C Standard
gives an explicit guarantee (gives an explicit requirement for any
conforming implementation) that in a certain subset of all situations
where a value of 0 is converted, the result will be a null pointer. The
implementation has no choice but converting _all_ zero values to null
pointers.

You are still confused. If you look again at the excerpt from the
standard that defines an "integer constant expression" you will note
that it is an expression with a value known to the compiler at COMPILE
TIME.

When you convert, with a cast, the value of an integer object to a
pointer, the value of that integer object is accessed and evaluated at
RUN TIME.

So the implementation-defined behavior can most certainly distinguish
between a "zero that is a null pointer constant" and a zero that is
not a null pointer constant, whatever that means, presumably the value
of some object that happens to equal 0.

It is probably not even allowable in the RUN TIME operation of
converting an integer value to a pointer with an appropriate cast,
that the implementation could check whether the value evaluates to 0
and if so, convert it to the bit pattern of a null pointer even if its
pattern for a null pointer is not all bits 0.

This is because, assuming an implementations has an integer type large
enough to hold a pointer, then:

-- a conversion integer -> pointer -> integer must end up with the
final integer having the same value as the original, and...

-- a conversion pointer -> integer -> pointer to same type must end up
with the final pointer containing the same address as the original.

So if an implementation had a representation for null pointers that
was not all bits 0, it could either convert integers with the value 0
to that null pointer representation, or integers with the bit pattern
of the null pointer to a null pointer, but not both, otherwise when
converting a null pointer back to an integer type it could not know
whether to generate 0 or the null pointer bit pattern in the
destination integer.
 
C

Charlie Gordon

This is because, assuming an implementations has an integer type large
enough to hold a pointer, then:

-- a conversion integer -> pointer -> integer must end up with the
final integer having the same value as the original, and...

Where does C99 state this ?
-- a conversion pointer -> integer -> pointer to same type must end up
with the final pointer containing the same address as the original.

That is not the language of the standard : it says nothing about such a
conversion.
All it says is that the result of converting a valid pointer to void* or char*
and back to the original pointer type will yield a pointer that compares equal
to the original pointer. "Containing the same address" is not necessary, nor is
it a concept defined in the standard.
So if an implementation had a representation for null pointers that
was not all bits 0, it could either convert integers with the value 0
to that null pointer representation, or integers with the bit pattern
of the null pointer to a null pointer, but not both, otherwise when
converting a null pointer back to an integer type it could not know
whether to generate 0 or the null pointer bit pattern in the
destination integer.

I don't think you can say that.
 
L

Lawrence Kirby

Thanks for the clarification. I think what I was trying to
say is, the implementation could define it as a hardware
exception (or some other condition that terminates the program).
(Is that right?)

The standard says that the *result* is implementation-defined. That means
that the operation has to produce a result. However that result may be a
trap representation, so a subsequent operation that uses the result can
produce undefined behaviour.

Lawrence
 
P

pete

Charlie said:
Where does C99 state this ?


That is not the language of the standard :
it says nothing about such a conversion.
All it says is that the result of converting a valid
pointer to void* or char*
and back to the original pointer type will yield a pointer that
compares equal
to the original pointer. "Containing the same address"
is not necessary, nor is it a concept defined in the standard.


I don't think you can say that.

I don't think the standard prohibits an implementation
from yielding the integer value of five,
for every pointer to integer conversion.
 
R

Richard Bos

Christian Bau said:
The "special handling" happens in cases like

I quote the Standard at you, and you deny that it says what it does say?
A null pointer constant is converted to a null pointer in _all_ cases
where it is converted to a pointer type. Which particular construct is
used to convert it is _not_ important. That is what the above quotation
of the Standard says. If you wish to deny this, present arguments.

Richard
 
C

Christian Bau

Jack Klein said:
You are still confused. If you look again at the excerpt from the
standard that defines an "integer constant expression" you will note
that it is an expression with a value known to the compiler at COMPILE
TIME.

Nonsense. The conversion is done at RUN TIME in the abstract machine.
The C Standard gives a requirement what the result has to be before the
compiler is even written, the compiler knows at compile time what the
result has to be, the conversion will most likely not generate any code,
which is allowed by the "as if" rule, but the conversion is done _at run
time_.
When you convert, with a cast, the value of an integer object to a
pointer, the value of that integer object is accessed and evaluated at
RUN TIME.

In the case

int i = 0;
char* p = (char *) i;

the compiler knows what the result will be exactly in the same way as in
the case

char* p = 0;

The same conversion happens in both cases. The only difference: In the
second case the C Standard gives a requirement that explicitely mentions
the case, in the first case the compiler knows that exactly the same
conversion happens, so the result must be the same.
So the implementation-defined behavior can most certainly distinguish
between a "zero that is a null pointer constant" and a zero that is
not a null pointer constant, whatever that means, presumably the value
of some object that happens to equal 0.

It is probably not even allowable in the RUN TIME operation of
converting an integer value to a pointer with an appropriate cast,
that the implementation could check whether the value evaluates to 0
and if so, convert it to the bit pattern of a null pointer even if its
pattern for a null pointer is not all bits 0.

Of course it is allowed.
This is because, assuming an implementations has an integer type large
enough to hold a pointer, then:

-- a conversion integer -> pointer -> integer must end up with the
final integer having the same value as the original, and...

Right.
-- a conversion pointer -> integer -> pointer to same type must end up
with the final pointer containing the same address as the original.
Right.
So if an implementation had a representation for null pointers that
was not all bits 0, it could either convert integers with the value 0
to that null pointer representation, or integers with the bit pattern
of the null pointer to a null pointer, but not both

Right.
otherwise when
converting a null pointer back to an integer type it could not know
whether to generate 0 or the null pointer bit pattern in the
destination integer.

Correct. If the representation for a null pointer is not all bits zero,
then conversions in both directions cannot leave the representation
unchanged.
 
C

Christian Bau

I quote the Standard at you, and you deny that it says what it does say?

I deny that you found what was relevant to the discussion.
A null pointer constant is converted to a null pointer in _all_ cases
where it is converted to a pointer type. Which particular construct is
used to convert it is _not_ important. That is what the above quotation
of the Standard says. If you wish to deny this, present arguments.

You are confusing two things. A. The C Standard says what the result of
converting a null pointer constant is. B. The C Standard gives a list of
situations, where an integer would normally not be allowed, except when
that integer is a null pointer constant, in which case it is converted
to an appropriate pointer.
 
R

Richard Bos

Christian Bau said:
You are confusing two things. A. The C Standard says what the result of
converting a null pointer constant is. B. The C Standard gives a list of
situations, where an integer would normally not be allowed, except when
that integer is a null pointer constant, in which case it is converted
to an appropriate pointer.

Right. C&V, or get lost.

Richard
 
S

S.Tobias

Christian Bau said:
In the case
int i = 0;
char* p = (char *) i;
the compiler knows what the result will be exactly in the same way as in
the case
char* p = 0;
The same conversion happens in both cases.
^^^^^^^^^^^^^^^^^^^
Prove it!

In 6.3.2.3 the Standard describes several types of conversions involving
pointer types. Just prove that conversion from null pointer constant
to a pointer type and conversion from integer type to pointer type
are the *same kind of conversions*, and I'm with you.

(Whether it happens and compile- or run-time, doesn't matter.)
 
S

S.Tobias

Jack Klein said:
On 28 Nov 2004 20:29:37 GMT, "S.Tobias"
The term 'access' is really only used in the C standard in conjunction
with the volatile qualifier, where the wording is unfortunately vague
enough that it can be construed several different ways.

I was thinking "access for value", referring to clauses such as
6.2.6 ("Representations ...") and 6.5#7 (aliasing rules).
The two statements above actually have defined behavior, but not for
the reason you might think. The language guarantees that a pointer to
structure, suitably cast, is also a pointer to its first member. So
in given that 'pb' holds any of the following:
- a pointer to any structure type whose first member is an int
- a pointer to an array of ints
- a pointer to a single int
...and the int pointed to has a valid value, the expressions, though
not recommended, will work as designed. The compiler must generate
code equivalent to *(int *)pb, and if there is actually an int there
all is well.

Just looking at the last expression makes me feel dizzy, this is because
*pb;
is definitely UB. Accessing `a' by use of unrelated type (struct B) lvalue
in the original expression doesn't look good, but indeed I haven't found
anything that would render it invalid.


(By accessing the whole struct I understood operation semantically
equivalent to:
*pb;
)

After careful reading of 6.3.2.1 ("Lvalues, arrays, ...") and 6.5.2.3
("Structure and union members") I came to the following conclusions.
Could you please check my reasoning.

I understand that "access for value" is when lvalue is converted
to value, as described in 6.3.2.1#2.

(*pb).i;

*pb is an lvalue (6.5.3.2#4). Since this lvalue is a left operand of `.'
operator, it is not converted to value (6.3.2.1#2), hence it is not an
object access for value. (*pb).i is an lvalue and in above expression
is converted to value.


&pb->i;

Similarly as above, pb->i is an lvalue, and since it is an operand of
the `&' operator, it is not converted to a value (6.3.2.1#2), so
the value of object pb->i is not accessed. Neither the whole struct
nor its member are accessed for value.

2. I see certain similarity between structs and arrays (in fact,
both are called "aggregates").
Why is it that for array:
&a[5];
doesn't constitute object access (6.5.3.2#3), whereas for struct:
&s.m;
&ps->m;
the expressions do constitute access?
Why is the language designed like this?

As I have shown above, I thought wrong before. The member of the struct
is not accessed, because in either case the lvalues are operands to `&'
operator and are not converted to values.

There are actually more differences than similarities between structs
[snip]

I completely agree with what you said. Sometimes I imagine
a struct as sort of an "array" which holds different objects,
and expression
s.member3; //starts from `member0'
is sort of conceptually corresponding to
a[3];
This is a very vague similarity and is a matter of personal POV.

Now let's back up to paragraph 1 of 6.5.3.2, which lists the
constraints for the unary '&' operator:
[begin quotation]
The operand of the unary & operator shall be either a function
designator, the result of a [] or unary * operator, or an lvalue that
designates an object that is not a bit-field and is not declared with
the register storage-class specifier.
[end quotation]
Notice that the expression under discussion,

...is none of these things. Specifically, the operand of the '&'
operator, '((doomdata*)0)->a' is:
- not a function designator
- not the result of a [] operator
- not the result of a unary * operator
- and, because of the null pointer, not an lvalue

I mildly disagree at the last point. 6.5.2.3#4 says that the result
of `->' operator is an lvalue. But this is not exactly what I want
to say.

The quotation you brought comes from Constraints. Lvalue is an expression
which *potentially* designates an object.
int *pi;
/*...*/
++(*pi);
In the last expression `pi' might point to an object or not; the compiler
cannot diagnose this. Nevertheless *pi is an lvalue expression.

For same reasons in:
++(*(int*)NULL);
I believe that *(int*)NULL is an lvalue expression (it is not a constraint
violation), though the compiler might indeed diagnose this early.

I think this example illustrates my point best:
int *pi = malloc(sizeof *pi);
*pi is an lvalue, although at this stage it doesn't point to any object.

Finally consider one last thing, namely that regardless of whether
there is an actual access to an object, the expression explicitly
performs pointer arithmetic on a null pointer, and such use of a null
pointer is undefined in and of itself.

I can believe it. The compiler internally must do something like
(int) ((char*)NULL + offset(doomdata, a))
But is it formally stated so? I can't think of any place where the
Standard describes member access as related to any pointer arithmetic.
 
C

Christian Bau

Right. C&V, or get lost.

C99 Final Draft:

6.5.9.2 Equality operators/constraints. "one operand is a pointer and
the other is a null pointer constant"

6.5.15.3 Conditional operator/constraints: "One of the following shall
hold for the second and third operands: " ... "one operand is a pointer
and the other is a null pointer constant"

6.5.16.1 Simple assignment/constraints: "the left operand is a pointer
and the right is a null pointer constant"

You are confusing this with 6.3.2.3 Pointers: "If a null pointer
constant is converted to a pointer type, the resulting pointer, called
anull pointer, isguaranteed to compare unequal to a pointer to anyobject
or function."
 
R

Richard Bos

Christian Bau said:
C99 Final Draft:

6.5.9.2 Equality operators/constraints. "one operand is a pointer and
the other is a null pointer constant"

6.5.15.3 Conditional operator/constraints: "One of the following shall
hold for the second and third operands: " ... "one operand is a pointer
and the other is a null pointer constant"

6.5.16.1 Simple assignment/constraints: "the left operand is a pointer
and the right is a null pointer constant"

That's not a list given by the Standard, that's a list of somewhat
related quotations from the Standard given by you. Of course, it's
incomplete.

Richard
 
S

S.Tobias

That's not a list given by the Standard, that's a list of somewhat
related quotations from the Standard given by you. Of course, it's
incomplete.

Besides that those quotes are taken from Constraints.

I've lost track what is actually discussed here and why those arguments
are being brought here; I think it started from the question if in
int *p = 0;
p == 0;
there's a special handling of expression `0'. To determine that we
have to go to Semantics, and there's nothing to show that this is true,
and that above "list" has any meaning at all.

I just can't see what it could prove either way. Integer constant with
value 0 is the only integer operand that is allowed above and it's
not possible to determine differences in other integer-to-pointer
conversions. The only expression that converts an integer to a pointer
and can have any integer operand, is I believe the cast expression.
 
C

Christian Bau

"S.Tobias said:
How do you infer that for each pair of source and destination types
there can be at most one conversion type?

Common sense?

One source type. One destination type. Number of possible conversions =
number of source types times number of destination types = 1*1 = 1.

Try giving an argument why there would be more than one possible
conversion.
 
C

Christian Bau

You could do us all a favour and tell us what is missing.
Besides that those quotes are taken from Constraints.

Of course they are. The Standard defines operators, and then under
"Constraints" it gives a list of all the operand combinations that are
allowed.
 
S

S.Tobias

Common sense?
One source type. One destination type. Number of possible conversions =
number of source types times number of destination types = 1*1 = 1.
Try giving an argument why there would be more than one possible
conversion.

Since I believe in C there is no example for that other than for
int->pointer, which is currently in question, I'll give an example from
C++, which is to illustrate the concept.

struct Base1 {};
struct Base2 {};
struct Derived : Base1, Base2 {};
Derived D, *pD = &D;
static_cast<Base2*>(pD); // (1)
reinterpret_cast<Base2*>(pD); // (2) , value unspecified

The source type is Derived* and the destination type is Base2*,
and there are two different (single!) conversions.

Only the first conversion is standard (and therefore can be implicit),
and the result points to sub-object Base2. The result of the second
is unspecified according to the C++ Standard, but can be (and I think
usually is) a pointer that points to the original object and has the
destination type, and corresponds to two conversions:
(Base2*)(void*)pD.

+++

Of course, that doesn't prove anything yet. It is just an illustration
that it is possible that more than one conversions may exist between
a pair of types, in the same manner as there can be many functions
that take an argument of one type and return the other type.
 
C

Christian Bau

"S.Tobias said:
Since I believe in C there is no example for that other than for
int->pointer, which is currently in question, I'll give an example from
C++, which is to illustrate the concept.

struct Base1 {};
struct Base2 {};
struct Derived : Base1, Base2 {};
Derived D, *pD = &D;
static_cast<Base2*>(pD); // (1)
reinterpret_cast<Base2*>(pD); // (2) , value unspecified

The source type is Derived* and the destination type is Base2*,
and there are two different (single!) conversions.

Only the first conversion is standard (and therefore can be implicit),
and the result points to sub-object Base2. The result of the second
is unspecified according to the C++ Standard, but can be (and I think
usually is) a pointer that points to the original object and has the
destination type, and corresponds to two conversions:
(Base2*)(void*)pD.

+++

Of course, that doesn't prove anything yet. It is just an illustration
that it is possible that more than one conversions may exist between
a pair of types, in the same manner as there can be many functions
that take an argument of one type and return the other type.

It is an illustration that more than one kind of conversion can exist in
a language that defines more than one kind of conversion. C is not such
a language. Even if there was more than one kind of conversion, where
the C Standard today says "converting a null pointer constant to a
pointer produces a null pointer", it would have to say "there is some
conversion which produces a null pointer" (that would be unusable), or
"a conversion of kind xxx produces a null pointer" (same situation as
now, replacing "conversion" with "conversion of kind xxx"), or "any
conversion", same situation as now, repeated for every conversion.
 
S

S.Tobias

Christian Bau said:
Since I believe in C there is no example for that other than for
int->pointer, which is currently in question, I'll give an example from
C++, which is to illustrate the concept. [snip]

Of course, that doesn't prove anything yet. It is just an illustration
that it is possible that more than one conversions may exist between
a pair of types, in the same manner as there can be many functions
that take an argument of one type and return the other type.
It is an illustration that more than one kind of conversion can exist in
a language that defines more than one kind of conversion. C is not such
a language.

I just wanted to give you a hint that a general assumption of one
conversion per pair of types is false, which is the thing I think
prevents you to accept that null-ptr-const conversion is a special case.
I can't convince you to anything, you have to convince yourself.

Have a look at the Rationale, which explains the intent of the Standard.
Whether the Standard is adequately worded, is another issue.
Even if there was more than one kind of conversion, where
the C Standard today says "converting a null pointer constant to a
pointer produces a null pointer", it would have to say "there is some
conversion which produces a null pointer" (that would be unusable), or
"a conversion of kind xxx produces a null pointer" (same situation as
now, replacing "conversion" with "conversion of kind xxx"), or "any
conversion", same situation as now, repeated for every conversion.

I don't quite get your point here, but I think you complain that the
Standard is not verbose enough. I agree, at at some points it might be
more communicative, but others will argue otherwise. Compare how this
issue is worded in C++ Standard; there's a footnote at `reinterpret_cast'
which emphasizes that null-ptr-const conversion is always a special case,
which is not true for other integer expressions.

+++++

My last few remarks:

The way the Standard defines integer-ptr conversions is really what
you want. The Std doesn't put any restrictions on implementation.
Consider a case where conversion is natural and symmetric:
ptr0xABCD <-> int0xABCD, and where null ptr is defined as ptr0xFFFF.
You don't want every 0 to be converted to ptr0xFFFF, because you couldn't
express ptr0x0000 in terms of any integer, which may be very valuable
for arithmetic. The Standard could have chosen a special key-word for the
"generalized" null pointer; it chose null pointer constant instead,
probably for other reasons. Null-ptr-const is just an easy way to obtain
a null pointer (not that it couldn't be obtained by other means, eg.:
strchr("", 'x'), but this is less elegant).

You have to remember that this special conversion applies only to
integer constant expressions, not necessarily constant expressions with
integer type:
void *p
p = 0; // okay
p = (int)0.; // okay (float constant directly cast to int)
p = (0 + 0); // okay
p = (int)0. + 0; // okay
p = (int)(0. + 0); // NOT okay (expression including uncast float)
The last one is an example of constant expression with integer type which
is not integer constant expression, its conversion is implementation
defined and must be explicit (there's constraint violation there).
gcc lets the last example through, como (properly) does not.

The reason why the Standard requires null-ptr-const as one of operands
of `=' (and others, that you quoted in parallel thread) is very simple:
because only this conversion is well defined. It is very probable
that a programmer writing "p = 0;" knows what he's doing. For other
integer expressions the conversion is possible if the implementation
defines it, but has to be explicit, thus requiring some work and thought
from the programmer.
 

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
474,156
Messages
2,570,878
Members
47,405
Latest member
DavidCex

Latest Threads

Top