Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

null pointer constant, address constant

115 views
Skip to first unread message

Szabolcs Nagy

unread,
Dec 18, 2012, 8:25:54 AM12/18/12
to
it seems the definition of 'null pointer constant'
relies on integer constant expressions (6.3.2.3p3)
http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3

so eg.

(void*)((char*)1 - 1)

is not a null pointer constant because pointer
arithmetics cannot give an integer constant expression

but at least this could be used in pointer initializers
since address constants are allowed there (6.6p7)
http://port70.net/~nsz/c/c11/n1570.html#6.6p7

but it seems

0 ? (void*)0 : (void*)0

is not a null pointer constant, nor an address constant

it is mildly unexpected that this cannot be used as a
null pointer constant
(this matters when determining the type of ?: (6.5.15)
expressions, assigning it to function pointers (6.5.16.1)
or comparing it to function pointers (6.5.9))

but (for me at least) it's more surprising that this is
not even a constant expression that can be used in
initializers, ie.

void *p = 0?(void*)0:(void*)0;

is undefined behaviour if i read section 6.6 correctly
(not even a constraint violation that requires a diagnostic)

another similar surprising case is

void *q = (void*)((char*)1 - (char*)1);

the initializer is not an address constant, not a null pointer
constant nor an address constant +- an integer constant
expression.

i wonder if my interpretation is right and if there is
some rational behind this

(my personal intuition is that integer constant expressions
should be defined more generously and may be the result of
pointer arithmetics if the pointers are casted integer
constants or the difference of address constants, then
these surprises would go away)

James Kuyper

unread,
Dec 18, 2012, 9:53:35 AM12/18/12
to
On 12/18/2012 08:25 AM, Szabolcs Nagy wrote:
> it seems the definition of 'null pointer constant'
> relies on integer constant expressions (6.3.2.3p3)
> http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3
>
> so eg.
>
> (void*)((char*)1 - 1)
>
> is not a null pointer constant because pointer
> arithmetics cannot give an integer constant expression
>
> but at least this could be used in pointer initializers
> since address constants are allowed there (6.6p7)

Two things to keep in mind:

When converting an arbitrary integer to a pointer type "the result is
implementation-defined, might not be correctly aligned, might not point
to an entity of the referenced type, and might be a trap
representation." (6.3.2.3p1)
Now, alignment cannot be an issue with char*, but trap representation
certainly is. Unless the compiler you're using tells you something
useful about where (char*)1 points, you're asking for big trouble trying
to use it.

Subtraction of an integer from a pointer is defined only for a pointer
that points at an element of an array, or one past the end of the array,
and only if the defined result of the subtraction would also be in that
array (6.5.6p8). Unless the compiler you're using defines that (char*)1
points at an element of an array, other than the first element of that
array, the subtraction has undefined behavior. If your compiler does
provide such a guarantee, then you're right, this could be used as a
pointer constant expression.

However, I get the impression that you were expecting

(char*)1-1 == (char*)0.

The standard says nothing of the kind - in fact, if that expression has
defined behavior, it's guaranteed to be false. Here's why: if the
implementation does define (char*)1 as pointing at an element, other
than the first, of an array, then (char*)1 - 1 will point at the
immediately preceding element of the array, and therefore, points at an
object. Now (char*)0 is a null pointer, and a null pointer can never
compare equal to a pointer to an object (6.3.2.3p3). Therefore, if the
implementation-defined value of (char*)1 is such as to give the
subtraction standard-defined behavior, that behavior is guaranteed to
NOT result in a pointer that compares equal to (char*)0.

> http://port70.net/~nsz/c/c11/n1570.html#6.6p7
>
> but it seems
>
> 0 ? (void*)0 : (void*)0
>
> is not a null pointer constant, nor an address constant

Correct. But it is a null pointer with the type 'void*'.

> it is mildly unexpected that this cannot be used as a
> null pointer constant
> (this matters when determining the type of ?: (6.5.15)

I may be missing something, but as far as I can tell, the rules given in
6.5.15p6 give the same result whether the second or third operand is a
null pointer constant, or merely a constant expression with the type
void* and a null value. Could you provide a counter-example?

However, that difference does make one case a constraint violation. The
expression

condition ? null_pointer_constant : function_pointer

is well-defined, and has the type of the function pointer, but

condition ? constant_null_pointer_to_void : function_pointer

is a constraint violation. (6.5.15p3)

> expressions, assigning it to function pointers (6.5.16.1)
> or comparing it to function pointers (6.5.9))

You're right on those two counts; both cases are constraint violations,
but would not be if it qualified as null pointer constant.

> but (for me at least) it's more surprising that this is
> not even a constant expression that can be used in
> initializers, ie.
>
> void *p = 0?(void*)0:(void*)0;
>
> is undefined behaviour if i read section 6.6 correctly
> (not even a constraint violation that requires a diagnostic)

That does seem a bit surprising, but I believe you're correct. Note,
however, that a constant expression is required only if 'p' has static
or thread storage duration. It's perfectly usable as an initializer for
a variable with automatic storage duration.

> another similar surprising case is
>
> void *q = (void*)((char*)1 - (char*)1);

And only a problem if q has static or thread storage duration.

> the initializer is not an address constant, not a null pointer
> constant nor an address constant +- an integer constant
> expression.

Well, given the potential problems with (char*)1, I don't see that as a
big problem. Even if (char*)1 is defined by the implementation as having
a valid value, the subtraction is an expression with a type of ptrdiff_t
and a value of 0. It doesn't qualify as an integer constant expression,
and it's conversion to void* is therefore not required to produce a null
pointer constant. It's covered only by the same rule (6.3.2.3p1) that
covers (char*)1, and it therefore has all of the same problems as (char*)1.

> i wonder if my interpretation is right and if there is
> some rational behind this

The places where integer constant expressions are required are places
where they must be evaluated at compile time to generate the right code.
This imposes a bit of a strain on the implementor, who would otherwise
be allowed to delay evaluation of such expressions until run-time. The
rules for integer constant expressions were intended to keep them
simple, to make them easy to identify and evaluate.
--
James Kuyper

Shao Miller

unread,
Dec 18, 2012, 2:02:42 PM12/18/12
to
On 12/18/2012 08:25, Szabolcs Nagy wrote:
>
> but it seems
>
> 0 ? (void*)0 : (void*)0
>
> is not a null pointer constant, nor an address constant
>
> it is mildly unexpected that this cannot be used as a
> null pointer constant
> (this matters when determining the type of ?: (6.5.15)
> expressions, assigning it to function pointers (6.5.16.1)
> or comparing it to function pointers (6.5.9))
>

It seems more useful as a "constant null pointer" than as a "null
pointer constant," which is trivial. This construct seems to allow you
to sneak out of NPC semantics if desired, while still using a null
pointer value, a 'void *' type, and no identifiable objects.

To follow your notes:

x ? ptr : CNP

now has type 'void *' instead of the type of 'ptr',

funcptr = CNP

contrasts a function type with an incomplete object type, and so do

funcptr == CNP
funcptr != CNP

> but (for me at least) it's more surprising that this is
> not even a constant expression that can be used in
> initializers, ie.
>
> void *p = 0?(void*)0:(void*)0;
>

Why not? 6.6p7 says an initializer can evaluate to an address constant.
6.6p9 says an address constant can be a null pointer. Your ternary
yields one or the other of two such address constants that were created
by a cast of a null pointer constant. Could you point out what I've missed?

> another similar surprising case is
>
> void *q = (void*)((char*)1 - (char*)1);
>

This doesn't surprise me. The most portable code cannot assume that
'(char *) 1' means anything. I would not expect an implementation to
deal with anything other than null pointer values and the addresses of
actual functions and objects, at translation-time. In the code you've
shared, it bounces out of the realm of portability temporarily with the
hope of returning to it, but I don't think it can be so.

- Shao Miller

Szabolcs Nagy

unread,
Dec 18, 2012, 3:06:19 PM12/18/12
to
James Kuyper <james...@verizon.net> wrote:
> On 12/18/2012 08:25 AM, Szabolcs Nagy wrote:
>> it seems the definition of 'null pointer constant'
>> relies on integer constant expressions (6.3.2.3p3)
>> http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3
>>
>> so eg.
>>
>> (void*)((char*)1 - 1)
>>
>> is not a null pointer constant because pointer
>> arithmetics cannot give an integer constant expression
>>
>> but at least this could be used in pointer initializers
>> since address constants are allowed there (6.6p7)
>
> Two things to keep in mind:
>
> When converting an arbitrary integer to a pointer type "the result is
> implementation-defined, might not be correctly aligned, might not point
> to an entity of the referenced type, and might be a trap
> representation." (6.3.2.3p1)

does not matter, your concerns only matter if the expression is
evaluated, but it is not:

sizeof( *(0?(double*)0:(void*)((char*)1 - 1)) )

is this equivalent to sizeof(double) or sizeof(void)?
(now that the expression after : is not a null pointer constant
we know the result is sizeof(void) and thus a constraint violation)

you don't need to evaluate the expression to use the "constness"
of it so any argument about trap representations and alignment is
moot

> However, I get the impression that you were expecting
>
> (char*)1-1 == (char*)0.
>
> The standard says nothing of the kind - in fact, if that expression has
> defined behavior, it's guaranteed to be false. Here's why: if the
> implementation does define (char*)1 as pointing at an element, other
> than the first, of an array, then (char*)1 - 1 will point at the
> immediately preceding element of the array, and therefore, points at an
> object. Now (char*)0 is a null pointer, and a null pointer can never
> compare equal to a pointer to an object (6.3.2.3p3). Therefore, if the

this is a valid argument

> I may be missing something, but as far as I can tell, the rules given in
> 6.5.15p6 give the same result whether the second or third operand is a
> null pointer constant, or merely a constant expression with the type
> void* and a null value. Could you provide a counter-example?

yes

(0 ? (double*)0 : (void*)0)

has type double*, while

(0 ? (double*)0 : (void*)1)

has type void*

to exploit this difference you can use sizeof as above


>> but (for me at least) it's more surprising that this is
>> not even a constant expression that can be used in
>> initializers, ie.
>>
>> void *p = 0?(void*)0:(void*)0;
>>
>> is undefined behaviour if i read section 6.6 correctly
>> (not even a constraint violation that requires a diagnostic)
>
> That does seem a bit surprising, but I believe you're correct. Note,

ok i was wrong about this, an address constant can be a
null pointer (not just null pointer constant)

> This imposes a bit of a strain on the implementor, who would otherwise
> be allowed to delay evaluation of such expressions until run-time. The
> rules for integer constant expressions were intended to keep them
> simple, to make them easy to identify and evaluate.

my problem is that they are not easy to identify

!(int)1.0
(void*)(1-1)

are integer constant expressions and null pointer constants

(int)!1.0
(void*)(char*)0
(char*)(void*)0
(void*)(void*)0
(void*)((char*)0-(char*)0)

are neither

Szabolcs Nagy

unread,
Dec 18, 2012, 3:09:46 PM12/18/12
to
Shao Miller <sha0....@gmail.com> wrote:
> On 12/18/2012 08:25, Szabolcs Nagy wrote:
>> but (for me at least) it's more surprising that this is
>> not even a constant expression that can be used in
>> initializers, ie.
>>
>> void *p = 0?(void*)0:(void*)0;
>>
>
> Why not? 6.6p7 says an initializer can evaluate to an address constant.
> 6.6p9 says an address constant can be a null pointer. Your ternary

you are rigth, thanks

i was wrong about initializers

James Kuyper

unread,
Dec 18, 2012, 3:39:46 PM12/18/12
to
On 12/18/2012 03:06 PM, Szabolcs Nagy wrote:
> James Kuyper <james...@verizon.net> wrote:
>> On 12/18/2012 08:25 AM, Szabolcs Nagy wrote:
>>> it seems the definition of 'null pointer constant'
>>> relies on integer constant expressions (6.3.2.3p3)
>>> http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p3
>>>
>>> so eg.
>>>
>>> (void*)((char*)1 - 1)
>>>
>>> is not a null pointer constant because pointer
>>> arithmetics cannot give an integer constant expression
>>>
>>> but at least this could be used in pointer initializers
>>> since address constants are allowed there (6.6p7)
>>
>> Two things to keep in mind:
>>
>> When converting an arbitrary integer to a pointer type "the result is
>> implementation-defined, might not be correctly aligned, might not point
>> to an entity of the referenced type, and might be a trap
>> representation." (6.3.2.3p1)
>
> does not matter, your concerns only matter if the expression is
> evaluated, but it is not:
>
> sizeof( *(0?(double*)0:(void*)((char*)1 - 1)) )

You didn't mention that in your original message.

...
>> I may be missing something, but as far as I can tell, the rules given in
>> 6.5.15p6 give the same result whether the second or third operand is a
>> null pointer constant, or merely a constant expression with the type
>> void* and a null value. Could you provide a counter-example?
>
> yes
>
> (0 ? (double*)0 : (void*)0)
>
> has type double*, while
>
> (0 ? (double*)0 : (void*)1)
>
> has type void*

You're right - I missed that.

...
>> This imposes a bit of a strain on the implementor, who would otherwise
>> be allowed to delay evaluation of such expressions until run-time. The
>> rules for integer constant expressions were intended to keep them
>> simple, to make them easy to identify and evaluate.
>
> my problem is that they are not easy to identify
>
> !(int)1.0
> (void*)(1-1)
>
> are integer constant expressions and null pointer constants

(void*)(1-1) is not an integer constant expression, though it is a null
pointer constant.

> (int)!1.0
> (void*)(char*)0
> (char*)(void*)0
> (void*)(void*)0
> (void*)((char*)0-(char*)0)
>
> are neither

While the list of things prohibited in ICEs is long, that fact is
normally only of importance to compiler writers, who must write code
that reliably and accurately detects them in the source code. The
contexts where ICEs are required (array dimensions, enumeration values,
and bit-field sizes) are not normally reasonable contexts for any
expressions that would push the limits on what is permitted.
Can you give an example where it was reasonable, in a context where an
ICE was required, to use an expression that pushed (or exceeded) the
limits on what is permitted for an ICE?

I can come up with only three different null pointer constant
expressions that most people would ever have any good reason to use in C:
0: for compatibility with C++
(void*)0: for better type-checking than you get with a plain 0
NULL: self-documenting, and a lot easier to search for than the other two.
Can you give any good reason for using any other null pointer constant
expressions? Can you claim that such situations are commonplace?

James Kuyper

unread,
Dec 18, 2012, 3:54:12 PM12/18/12
to
"An address constant ... shall be created explicitly using
the unary & operator or an integer constant cast to pointer type, or
implicitly by the use of an expression of array or function type."
(6.6p9). (void*)0 fits the second option, so that expression contains
two address constants as sub-expressions, but the ?: expression itself
doesn't fit any of the three options.

Shao Miller

unread,
Dec 18, 2012, 4:15:19 PM12/18/12
to
On 12/18/2012 15:06, Szabolcs Nagy wrote:
>>
>> When converting an arbitrary integer to a pointer type "the result is
>> implementation-defined, might not be correctly aligned, might not point
>> to an entity of the referenced type, and might be a trap
>> representation." (6.3.2.3p1)

(Using your reference, I think James meant p5.)

>
> does not matter, your concerns only matter if the expression is
> evaluated, but it is not:
>
> sizeof( *(0?(double*)0:(void*)((char*)1 - 1)) )
>
> is this equivalent to sizeof(double) or sizeof(void)?
> (now that the expression after : is not a null pointer constant
> we know the result is sizeof(void) and thus a constraint violation)
>
> you don't need to evaluate the expression to use the "constness"
> of it so any argument about trap representations and alignment is
> moot
>

Why don't you need to? It might be worth noting that this discussion
happens under 6.3 "Conversions" and not under 6.5 "Expressions".

If you're correct, does that mean that an implementation is not allowed
to convert '(char *) 1' to a different value every time it is used in
the source code?

Possibly related, if we have:

int x = 42;
x -= func_asking_user_for_number_and_they_provide_42();
char * ptr = (char *) x;

Does 'ptr' contain a null pointer value? We know that a null pointer
constant can be used to produce a null pointer value, but what about the
simple value 0 without the additional properties that an NPC has? It
seems to me that it might be useful to distinguish between the two...

On x86 with BIOS, physical address 0 would be the first byte of the
Interrupt Vector Table. If we wish to allow access to this as an
object, then surely we would want to distinguish that object from a null
pointer, which oughtn't to point to any object.

Similarly, if the implementation defines that '(char *) 1' points to the
second element of an array, we might expect that '((char *) 1) - 1'
points to the first element of the array, rather than suddenly becoming
a null pointer value.

One thing that might be worth noting is that "result" seems to be used
to mean one or more of "type" and "value" and "behaviour" in Standard C.
So even if the type is defined for your sizeof-with-ternary example,
it might still be that "the result"-ing behaviour is
implementation-defined. "Result" is a bit too loose, I think, sometimes.

I could be misunderstanding.

- Shao Miller

Shao Miller

unread,
Dec 18, 2012, 4:18:58 PM12/18/12
to
If you'd unplonk me, I might suggest that you review the second sentence
of p7, as well as the syntax in p1. Perhaps someone else can suggest it.

- Shao Miller

Keith Thompson

unread,
Dec 18, 2012, 6:52:10 PM12/18/12
to
Shao Miller <sha0....@gmail.com> writes:
[...]
> Possibly related, if we have:
>
> int x = 42;
> x -= func_asking_user_for_number_and_they_provide_42();
> char * ptr = (char *) x;
>
> Does 'ptr' contain a null pointer value?

Maybe.

> We know that a null pointer
> constant can be used to produce a null pointer value, but what about the
> simple value 0 without the additional properties that an NPC has? It
> seems to me that it might be useful to distinguish between the two...

The standard says that a constant arithmetic expression with the value
0, converted to a pointer type, yields a null pointer. It does *not*
say the same thing about a non-constant arithmetic expression with the
value 0.

Consider a system that represents null pointers as 0xFFFFFFFF
(let's assume for simplicity that all pointer types have the same
representation). The standard requires `(char*)0` yields a null
pointer, which means the conversion is non-trivial, though it takes
place at compile time. But any conversion that's *not* required
to yield a null pointer can simply reinterpret the representation.
So `(char*)zero`, where `zero` is a variable whose value is currently
0, would yield a pointer with an all-bits-zero representation,
*not* a null pointer. On the other hand, `(char*)0xFFFFFFFF`
would (happen to) yield a null pointer value, though it's not a
null pointer constant.

There are myriads of other possible implementations where `(char*)zero`
does not yield a null pointer. All one really needs to know is that the
standard doesn't define the behavior; concrete (but hypothetical)
examples can be useful in understanding why the standard does or does
not make certain guarantees.

> On x86 with BIOS, physical address 0 would be the first byte of the
> Interrupt Vector Table. If we wish to allow access to this as an
> object, then surely we would want to distinguish that object from a null
> pointer, which oughtn't to point to any object.

A program that dereferences physical address 0 (which, BTW, is typically
impossible in hosted implementations with virtual memory) most likely
has undefined behavior. It's perfectly valid for the result of that
undefined behavior to be that it accesses the Interrupt Vector Table.

[...]

--
Keith Thompson (The_Other_Keith) ks...@mib.org <http://www.ghoti.net/~kst>
Will write code for food.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"

Shao Miller

unread,
Dec 18, 2012, 7:12:48 PM12/18/12
to
On 12/18/2012 18:52, Keith Thompson wrote:
> Shao Miller <sha0....@gmail.com> writes:
> [...]
>> Possibly related, if we have:
>>
>> int x = 42;
>> x -= func_asking_user_for_number_and_they_provide_42();
>> char * ptr = (char *) x;
>>
>> Does 'ptr' contain a null pointer value?
>
> Maybe.
>

And maybe not.

>> We know that a null pointer
>> constant can be used to produce a null pointer value, but what about the
>> simple value 0 without the additional properties that an NPC has? It
>> seems to me that it might be useful to distinguish between the two...
>
> The standard says that a constant arithmetic expression with the value
> 0, converted to a pointer type, yields a null pointer. It does *not*
> say the same thing about a non-constant arithmetic expression with the
> value 0.
>

That's rather what I was suggesting to the original poster.

> Consider a system that represents null pointers as 0xFFFFFFFF
> (let's assume for simplicity that all pointer types have the same
> representation). The standard requires `(char*)0` yields a null
> pointer, which means the conversion is non-trivial, though it takes
> place at compile time. But any conversion that's *not* required
> to yield a null pointer can simply reinterpret the representation.
> So `(char*)zero`, where `zero` is a variable whose value is currently
> 0, would yield a pointer with an all-bits-zero representation,
> *not* a null pointer. On the other hand, `(char*)0xFFFFFFFF`
> would (happen to) yield a null pointer value, though it's not a
> null pointer constant.
>

Good elaboration. Precisely what I was thinking. I am a tad worried
that the original poster is confusing "null pointer value" with "the
zero address in a linear address space," and casts of integers to
pointers with "pointing to particular addresses in a linear address space."

> There are myriads of other possible implementations where `(char*)zero`
> does not yield a null pointer. All one really needs to know is that the
> standard doesn't define the behavior; concrete (but hypothetical)
> examples can be useful in understanding why the standard does or does
> not make certain guarantees.
>
>> On x86 with BIOS, physical address 0 would be the first byte of the
>> Interrupt Vector Table. If we wish to allow access to this as an
>> object, then surely we would want to distinguish that object from a null
>> pointer, which oughtn't to point to any object.
>
> A program that dereferences physical address 0 (which, BTW, is typically
> impossible in hosted implementations with virtual memory) most likely
> has undefined behavior. It's perfectly valid for the result of that
> undefined behavior to be that it accesses the Interrupt Vector Table.
>
> [...]
>

Agreed. My "surely" was intended to suggest a possible reason for _why_
the Standard makes it implementation-defined. In free-standing
boot-loaders like Syslinux, it's really rather useful for the undefined
behaviour of _using_ a null pointer (even when constructed directly with
a null pointer constant) to happen to do what you might desire on x86
with BIOS*.

- Shao Miller

Off-topic post-script: *This is an example of "a real program that
depends on undefined behaviour" (by Standard C, but obviously defined by
the implementation)... The very phrase that caused James K. to plonk
me. Oops! :)

Szabolcs Nagy

unread,
Dec 18, 2012, 7:44:14 PM12/18/12
to
James Kuyper <james...@verizon.net> wrote:
> On 12/18/2012 03:06 PM, Szabolcs Nagy wrote:
>> does not matter, your concerns only matter if the expression is
>> evaluated, but it is not:
>>
>> sizeof( *(0?(double*)0:(void*)((char*)1 - 1)) )
>
> You didn't mention that in your original message.

sorry

> Can you give an example where it was reasonable, in a context where an
> ICE was required, to use an expression that pushed (or exceeded) the
> limits on what is permitted for an ICE?

it is required for an elaborate hack to workaround a
silliness of the standard that is tgmath.h

(ie. i have an implementation of tgmath.h which is
done with minimal compiler support using the typeof
extension present in many compilers)
(yes c11 _Generic could solve the issue but is not
practical as it is not supported on the compilers i
care about and i cannot modify the compilers, i'm
only implementing the standard library)

while testing the implementation it turned out that the
tested compilers disagreed about what is an ICE or a
null pointer constant (so it's not that trivial matter
after all)

eg on some compilers

#define __is_integral(x) !((1?1:x)/2)
__is_integral(1.0)

is an ICE and null pointer constant on others not

0 ? (double*)0 : 0 ? (void*)0 : (void*)0

has type double* on some compilers on others void*

none of the tested compilers are completely standard
conformant (gcc, clang) which means i can implement
tgmath.h with typeof only, if they were conformant i
could not

Shao Miller

unread,
Dec 18, 2012, 7:51:17 PM12/18/12
to
In case it's as clear as mud (a gift, to be sure), I was trying to
simultaneously support:

1. An x86 with BIOS implementation _might_ wish to be able to
distinguish a null pointer from "physical address 0", à la your 0xFFFFFFFF.

2. An x86 with BIOS implementation _might_ wish to be able to _use_ a
null pointer without crashing, à la accessing the IVT.

Both of these are supported by having null pointer constants produce
null pointers, and non-zero integer constants and non-constant integers
cast to pointers having implementation-defined results.

- Shao Miller

Keith Thompson

unread,
Dec 18, 2012, 8:02:57 PM12/18/12
to
Keith Thompson <ks...@mib.org> writes:
[...]
> The standard says that a constant arithmetic expression with the value
> 0, converted to a pointer type, yields a null pointer. It does *not*
> say the same thing about a non-constant arithmetic expression with the
> value 0.

Correction: I should have written "integer constant expression" rather
than "constant arithmetic expression".

James Kuyper

unread,
Dec 18, 2012, 8:24:59 PM12/18/12
to
On 12/18/2012 07:44 PM, Szabolcs Nagy wrote:
...
>> Can you give an example where it was reasonable, in a context where an
>> ICE was required, to use an expression that pushed (or exceeded) the
>> limits on what is permitted for an ICE?
>
> it is required for an elaborate hack to workaround a
> silliness of the standard that is tgmath.h
>
> (ie. i have an implementation of tgmath.h which is
> done with minimal compiler support using the typeof
> extension present in many compilers)
> (yes c11 _Generic could solve the issue but is not
> practical as it is not supported on the compilers i
> care about and i cannot modify the compilers, i'm
> only implementing the standard library)
>
> while testing the implementation it turned out that the
> tested compilers disagreed about what is an ICE or a
> null pointer constant (so it's not that trivial matter
> after all)

That is, I hope you'll admit, a rather unusual situation.

> eg on some compilers
>
> #define __is_integral(x) !((1?1:x)/2)
> __is_integral(1.0)
>
> is an ICE and null pointer constant on others not

Because 1.0 is not the immediate operand of a cast, that doesn't qualify
as an ICE, and therefore does not qualify as an NPC. The compilers that
disagree are wrong.

How did you use this macro, such that it needed to be an ICE or an NPC?

> 0 ? (double*)0 : 0 ? (void*)0 : (void*)0
>
> has type double* on some compilers on others void*

The inner ?: expression is a constant expression with a null pointer
value, but not a null pointer constant. Therefore, the outer ?:
expression should have the type void*.

> none of the tested compilers are completely standard
> conformant (gcc, clang) which means i can implement
> tgmath.h with typeof only, if they were conformant i
> could not

I believe that gcc can fully conform to C90, and probably to C99, with
the right compiler options. I thought the same was true of clang, but
I'm not as familiar with that compiler. If you need to use the compiler
in a non-conforming mode, I suppose it doesn't help to know that it also
has a conforming mode.
--
James Kuyper

Shao Miller

unread,
Dec 18, 2012, 8:37:27 PM12/18/12
to
On 12/18/2012 19:44, Szabolcs Nagy wrote:
>
> while testing the implementation it turned out that the
> tested compilers disagreed about what is an ICE or a
> null pointer constant (so it's not that trivial matter
> after all)
>
> eg on some compilers
>
> #define __is_integral(x) !((1?1:x)/2)
> __is_integral(1.0)
>
> is an ICE and null pointer constant on others not
>

So if I can follow along, '1.0' is unsuffixed, so it has type
'double'[6.4.4.2p4]. The usual arithmetic conversions cause the value
of the ternary's second (middle) operand to be converted to
'double'[6.3.1.8p1, 6.5.15p5]. That'd be '1.0' again. Then division by
'2' yields '0.5'. Then '0.5 == 0' yields 0[6.5.3.3p5].

Oh, but 6.6p6 wants '1.0' to be the operand of a cast, rather than an
operand of the ternary operator. 6.6p10 appears to grant license for
the discrepancy you observe with different compilers.

> 0 ? (double*)0 : 0 ? (void*)0 : (void*)0
>
> has type double* on some compilers on others void*
>

Would you be so kind as to mention which compiler (and mode) says this
has type 'double *'? It sure seems like it ought to have type 'void *',
to me. Even if 6.6p10 allows '(0 ? ((void *) 0) : ((void *) 0))' to be
a constant expression, it doesn't seem to qualify as an NPC, as it
wouldn't be an ICE nor an ICE cast to 'void *'. Maybe it warrants
clarification from the compiler team?

Very interesting. :)

- Shao Miller

Szabolcs Nagy

unread,
Dec 19, 2012, 5:27:11 AM12/19/12
to
James Kuyper <james...@verizon.net> wrote:
> On 12/18/2012 07:44 PM, Szabolcs Nagy wrote:
>> #define __is_integral(x) !((1?1:x)/2)
>
> How did you use this macro, such that it needed to be an ICE or an NPC?

typeof(0?(double*)0:(void*)!__is_integral(x))

or sizeof with dereference, but then it may be sizeof(void)

now i found a conformant and NPC solution so the case is closed:

#define __is_integral(x) (sizeof((x)+1.0f)!=sizeof((x)+1ULL))

float must be ieee 32bit format otherwise the whole math
implementation is invalid and unsigned long long must be
assumed to be (at least) 64bit

James Kuyper

unread,
Dec 19, 2012, 7:15:22 AM12/19/12
to
Well, if it doesn't issue a diagnostic because of your use of 1ULL, it
can't be fully conforming to C90, and if you need to provide your own
<tgmath.h>, it can't be fully conforming to any later version of the
standard, so making one assumption not guaranteed by the standard is not
unreasonable.
--
James Kuyper

christian.bau

unread,
Dec 20, 2012, 8:34:14 AM12/20/12
to
On Dec 18, 11:52 pm, Keith Thompson <ks...@mib.org> wrote:

> The standard says that a constant arithmetic expression with the value
> 0, converted to a pointer type, yields a null pointer.  It does *not*
> say the same thing about a non-constant arithmetic expression with the
> value 0.

It doesn't have to. The standard doesn't have to state something
explicitly if it follows from other rules that are stated.

Converting _any_ integer to _any_ pointer gives an implementation-
defined result. "implementation defined" means it is defined. So the
implementation must define what you get if you write "int x = 0; char*
p = (char *) x; char* q = (char *) ++x; char* r = (char *) ++x;".
There is also the requirement for the special case that an integer is,
by chance, a null pointer constant, where the result must be a null
pointer. So whatever definition the implementation gives, it must give
a definition that converts 0 to a null pointer if the zero was a null
pointer constant, therefore the definition must convert all zeroes to
null pointers.

James Kuyper

unread,
Dec 20, 2012, 10:33:31 AM12/20/12
to
On 12/20/2012 08:34 AM, christian.bau wrote:
> On Dec 18, 11:52�pm, Keith Thompson <ks...@mib.org> wrote:
>
>> The standard says that a constant arithmetic expression with the value
>> 0, converted to a pointer type, yields a null pointer. �It does *not*
>> say the same thing about a non-constant arithmetic expression with the
>> value 0.
>
> It doesn't have to. The standard doesn't have to state something
> explicitly if it follows from other rules that are stated.
>
> Converting _any_ integer to _any_ pointer gives an implementation-
> defined result.

Not so. "An integer may be converted to any pointer type. Except as
previously specified, the result is implementation-defined, ..."
6.3.2.3p5. Since null pointer constants are described in 6.3.2.3p4, they
are covered by "Except as previously specified". Conversion of
non-constant arithmetic expressions with a value of 0 is NOT covered by
that exception. This means that the implementation-defined result of
such a conversion need not match the standard-defined result for null
pointer constants.
--
James Kuyper

Shao Miller

unread,
Dec 20, 2012, 12:17:02 PM12/20/12
to
On 12/20/2012 08:34, christian.bau wrote:
>
> Converting _any_ integer to _any_ pointer gives an implementation-
> defined result. "implementation defined" means it is defined. So the
> implementation must define what you get if you write "int x = 0; char*
> p = (char *) x; char* q = (char *) ++x; char* r = (char *) ++x;".
> There is also the requirement for the special case that an integer is,
> by chance, a null pointer constant, where the result must be a null
> pointer. So whatever definition the implementation gives, it must give
> a definition that converts 0 to a null pointer if the zero was a null
> pointer constant, therefore the definition must convert all zeroes to
> null pointers.
>

There's a jump here that I don't follow, just at the "therefore". "So
whatever V2 the IM gives, it must give V2 == NULL for V1 == NPC,
therefore the IM must give V2 == NULL for V1 != NPC, also."

Since the implementation can _always_ distinguish between an NPC and a
non-NPC, can't it satisfy the requirement without having non-NPC 0
convert to a null pointer? It'd seem rather useful to distinguish
between the two, actually...

A debugger for x86 with BIOS could compare as unequal its own internal
null pointers with a user-input-derived pointer to the Interrupt Vector
Table. (For example, and not that anyone does this.)

- Shao Miller

James Kuyper

unread,
Dec 20, 2012, 12:28:41 PM12/20/12
to
After thinking about it a little while, I realized I hadn't worded that
quite right, and that my citation was incorrect. Here's a corrected version:

Since null pointers are described in 6.3.2.3p3, they are covered by
"Except as previously specified". Conversions of integer expressions
with a value of 0 to pointer types which are not covered by 6.3.2.3p3
are therefore not covered by that exception, and need not be defined by
the implementation to produce the same result.

I think that you're assuming that there's some kind of consistency
requirement imposed by the standard on the implementation's definition
of the result. For instance, that it is determined solely by the
integer's value and the type of the pointer. The standard imposes no
such requirement. The result could depend upon the type of the integer.
It could depend upon the kind of expression used to produce that
integer. It could depend upon the phase of the moon, and whether or not
today is Friday the 13th. It could be completely random. I can't come up
with a good reason for any of those things to be true, but it would not
render the implementation non-conforming if they were.

If there were such a consistency requirement, it would depend upon the
precise wording of that requirement whether or not it requires
consistency between the result, when it's implementation-defined, and
the result when the implementation has no obligation to provide a
definition, because it's standard-defined (by 6.3.2.3p3). However, that
issue doesn't come up, because there is no such consistency requirement.

Keith Thompson

unread,
Dec 20, 2012, 3:10:17 PM12/20/12
to
"christian.bau" <christ...@cbau.wanadoo.co.uk> writes:
> On Dec 18, 11:52 pm, Keith Thompson <ks...@mib.org> wrote:
>> The standard says that a constant arithmetic expression with the value
>> 0, converted to a pointer type, yields a null pointer.  It does *not*
>> say the same thing about a non-constant arithmetic expression with the
>> value 0.
>
> It doesn't have to. The standard doesn't have to state something
> explicitly if it follows from other rules that are stated.

I agree with that, but it doesn't apply here.

> Converting _any_ integer to _any_ pointer gives an implementation-
> defined result.

Right (except that the result of converting a constant arithmetic
expression with the value 0 to a pointer is not implementation-defined).

> "implementation defined" means it is defined. So the
> implementation must define what you get if you write "int x = 0; char*
> p = (char *) x; char* q = (char *) ++x; char* r = (char *) ++x;".

Right. Of course that definition can be given in very general terms.

> There is also the requirement for the special case that an integer is,
> by chance, a null pointer constant, where the result must be a null
> pointer.

Right (though I'm not sure what the phrase "by chance" adds to this;
most programmers don't flip a coin to decide whether to use a null
pointer constant or not).

> So whatever definition the implementation gives, it must give
> a definition that converts 0 to a null pointer if the zero was a null
> pointer constant,

Not really. The conversion of a CAE with value 0 to a pointer is *not*
implementation-defined. Look at N1570 6.3.2.3. Paragraph 3 says:

An integer constant expression with the value 0, or such an
expression cast to type void *, is called a *null pointer
constant*. If a null pointer constant is converted to a
pointer type, the resulting pointer, called a *null pointer*,
is guaranteed to compare unequal to a pointer to any object
or function.

This defines the terms "null pointer constant" and "null pointer".
(It's an incomplete definition of "null pointer", since there are ways
to obtain null pointers without using a null pointer constant, but
that's beside the point.)

And paragraph 5 says:

An integer may be converted to any pointer type. Except as
previously specified, the result is implementation-defined,
[...]

So it's only integer-to-pointer conversions *other than* those
already defined in paragraph 3 (null pointer constants) that
are implementation-defined. An implementation's documentation
needn't say anything about the result of converting a constant
0 to a pointer type; it's already implied by stating that it's a
conforming implementation.

> therefore the definition must convert all zeroes to
> null pointers.

This turns out not to be correct. It's certainly very common that
converting a non-constant integer expression with value 0 to a
pointer type yields a null pointer, but it's not required.

Consider a hypothetical implementation with the following
characteristics:

All pointer types have the same representation.

A null pointer is represented as all-bits-one (not as the more
common all-bits-zero).

Except as specified by the definition of "null pointer constant",
conversion of an integer value to a pointer type simply copies
or reinterprets the bits of the integer's representation.
(If the integer is smaller than a pointer, it's zero-extended;
if it's larger, high-order bits are ignored.)

For purposes of the following demonstration, printf's "%p"
format produces the representation of the pointer in hexadecimal.

In this implementation, the following program:

#include <stdio.h>

static void show(char *name, void *p) {
printf("%-15s = %p is %sa null pointer\n",
name, p, p == NULL ? "" : "NOT ");
}

int main(void) {
unsigned int zero = 0;
unsigned int all_ones = -1;
show("(void*)0", (void*)0);
show("(void*)zero", (void*)zero);
show("(void*)all_ones", (void*)all_ones);
return 0;
}

would produce this output:

(void*)0 = 0xffffffff is a null pointer
(void*)zero = 0x00000000 is NOT a null pointer
(void*)all_ones = 0xffffffff is a null pointer

How would this implementation violate the C standard?

The point of controversy is whether an integer-to-pointer conversion
for a constant 0 is required to yield the same result as a conversion
for a non-constant 0. I assert that it isn't.

Phil Carmody

unread,
Dec 20, 2012, 8:03:05 PM12/20/12
to
Szabolcs Nagy <n...@port70.net> writes:
> James Kuyper <james...@verizon.net> wrote:
> > On 12/18/2012 07:44 PM, Szabolcs Nagy wrote:
> >> #define __is_integral(x) !((1?1:x)/2)
> >
> > How did you use this macro, such that it needed to be an ICE or an NPC?
>
> typeof(0?(double*)0:(void*)!__is_integral(x))
>
> or sizeof with dereference, but then it may be sizeof(void)
>
> now i found a conformant and NPC solution so the case is closed:
>
> #define __is_integral(x) (sizeof((x)+1.0f)!=sizeof((x)+1ULL))
>
> float must be ieee 32bit format otherwise the whole math
> implementation is invalid

Woh, when did that happen? I have a box which has non-IEEE754 floats,
and it aren't even in 36-bit lala-land.

Phil
--
I'm not saying that google groups censors my posts, but there's a strong link
between me saying "google groups sucks" in articles, and them disappearing.

Oh - I guess I might be saying that google groups censors my posts.

Tim Rentsch

unread,
Dec 21, 2012, 12:05:48 AM12/21/12
to
IMO this is begging the question. The point of Christian's
comments is to argue that what happens when converting an integer
value of zero must be consistent in the case of a null pointer
constant (which also has an integer value of zero). Reading
just the Standard, I think a reasonable case can be made for
either point of view.

Despite that, the conclusion James reaches agrees with what the
committee intended. Support for this can be found in the Rationale
document (at the bottom of page 48 in the copy I have, V5.10).
I still believe though that the Standard by itself is not enough
to give an overwhelming argument either way; otherwise why would
it need to be mentioned in the Rationale document?

Tim Rentsch

unread,
Dec 21, 2012, 12:13:04 AM12/21/12
to
Longer, but still begging the same question. The text of the
Standard does not produce this conclusion clearly enough. At
the very least a clarifying footnote is needed.

Tim Rentsch

unread,
Dec 21, 2012, 12:25:42 AM12/21/12
to
> are implementation-defined. [snip elaboration]

The question is whether the case previously covered implies a
conversion for the value zero. IMO that quesstion can be argued
either way, ie, the Standard is not clear enough. I agree with
your conclusion as to intent, because of a statement found in the
Rationale document, but I don't think an airtight argument can be
made based just on the text in the Standard. Furthermore the
conclusion, even if it does reflect the intent of the committee,
is unexpected and counter-intuitive; surely it is a deficiency
of the Standard that such a circumstance exists without any
overt mention, and that deficiency deserves acknowledging -- we
are, after all, discussing the point in comp.std.c.

James Kuyper

unread,
Dec 21, 2012, 12:13:10 PM12/21/12
to
On 12/20/2012 08:03 PM, Phil Carmody wrote:
> Szabolcs Nagy <n...@port70.net> writes:
...
>> now i found a conformant and NPC solution so the case is closed:
>>
>> #define __is_integral(x) (sizeof((x)+1.0f)!=sizeof((x)+1ULL))
>>
>> float must be ieee 32bit format otherwise the whole math
>> implementation is invalid
>
> Woh, when did that happen? I have a box which has non-IEEE754 floats,
> and it aren't even in 36-bit lala-land.

I could be mistaken, but I assumed when he posted that, that he was
talking about his own math implementation. He is, after all,
implementing <tgmath.h>, so he's fiddling around in the implementor's
domain, even if he isn't himself an implementor of C. His math
implementation apparently makes assumptions that would prevent it from
being portable to your box. For the "whole math implementation" to be
invalid, those assumptions must apparently be very pervasive - I can't
imagine why that would be the case.

Szabolcs Nagy

unread,
Jan 8, 2013, 7:44:03 PM1/8/13
to
Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
> Szabolcs Nagy <n...@port70.net> writes:
>> float must be ieee 32bit format otherwise the whole math
>> implementation is invalid
>
> Woh, when did that happen? I have a box which has non-IEEE754 floats,
> and it aren't even in 36-bit lala-land.

happened in c99

the only way to have usable floating-point in c currently is to assume
c99 annex f (which is unfortunately not widely supported by compilers)
(with annex f float is ieee binary32 and double is ieee binary64)

without annex f there is no guarantee about the precision (and various
other properties) of float operations which is not adequate for
practical usage
(basic assumptions might not hold, eg. .2+.3 > .4 can be false, which
makes writing robust numerical algorithms hard/impossible)

(one can of course rely on the implementation defined floating-point
behaviour of the given platform, but that's outside the semantics
specified by c hence not portable, the necessary amount of ifdeffery
to make up for that is impractical)

some ppl think that weak semantics for floating-point arithmetics
is enough to write working numeric code, but the history of the
previous failed attempts at that hints otherwise

James Kuyper

unread,
Jan 8, 2013, 8:21:40 PM1/8/13
to
On 01/08/2013 07:44 PM, Szabolcs Nagy wrote:
> Phil Carmody <thefatphi...@yahoo.co.uk> wrote:
>> Szabolcs Nagy <n...@port70.net> writes:
>>> float must be ieee 32bit format otherwise the whole math
>>> implementation is invalid
>>
>> Woh, when did that happen? I have a box which has non-IEEE754 floats,
>> and it aren't even in 36-bit lala-land.
>
> happened in c99

What happened in C99 is that additional guarantees were added, but only
if __STDC_IEC_559__ was pre-#defined by the compiler. Before C99, IEC
60559 (==IEEE 754) platforms had the same lack of guarantees from the C
standard that non-IEEE754 platforms still have.

> the only way to have usable floating-point in c currently is to assume
> c99 annex f (which is unfortunately not widely supported by compilers)
> (with annex f float is ieee binary32 and double is ieee binary64)
>
> without annex f there is no guarantee about the precision (and various
> other properties) of float operations which is not adequate for
> practical usage
> (basic assumptions might not hold, eg. .2+.3 > .4 can be false, which
> makes writing robust numerical algorithms hard/impossible)

More accurately, without annex F, the C standard provides no guarantees
about those things. However, it's entirely possible for something other
than the C standard (such as the documentation for the compiler) to
provide all of the guarantees needed to make the implementation usable,
even if it's not compatible with IEEE 754.

> (one can of course rely on the implementation defined floating-point
> behaviour of the given platform, but that's outside the semantics
> specified by c hence not portable, the necessary amount of ifdeffery
> to make up for that is impractical)

Whether it's impractical depends entirely upon the application. If the
application is specifically targeted to a particular non-IEEE 754
platform (such as, presumably, Phil Carmody's applications), no
#ifdeffery is needed (except possibly for #error directives indicating
that it's being compiled for the wrong platform).

Too many people seem to think that if a program relies upon something
that's not guaranteed by the C standard, it's not useful. Not all code
needs to be portable to all platforms that have a conforming
implementation of C.

> some ppl think that weak semantics for floating-point arithmetics
> is enough to write working numeric code, but the history of the
> previous failed attempts at that hints otherwise,
--
James Kuyper

Eric Sosman

unread,
Jan 8, 2013, 8:46:50 PM1/8/13
to
On 1/8/2013 8:21 PM, James Kuyper wrote:
>[...]
> Too many people seem to think that if a program relies upon something
> that's not guaranteed by the C standard, it's not useful. Not all code
> needs to be portable to all platforms that have a conforming
> implementation of C.

The Rationale says this, explicitly.

Also, "portability" is not a Boolean attribute. I do not think
it is even a merely scalar attribute.

--
Eric Sosman
eso...@comcast-dot-net.invalid
0 new messages