Ternary Conditional Fun Macros for Any Type


S

Shao Miller

Well here's another possibly curious/interesting use of the ternary
conditional operator.

The 'destplus1_plus_sourceplus3' macro takes two lvalues with any
type.

It creates "scratch space" for a copy of each object. The scratch
space for each is an array of 'char' that is twice the 'sizeof' the
object.

Somewhere within such a scratch space is a position which will be
properly aligned for the type of object, since the 'sizeof' the object
must be a multiple of the alignment of that type. Why? Because
otherwise an array of that type would produce objects that were
improperly aligned. That would be silly.

It calculates the position within the scratch space which is both
[properly aligned] and [a distance away from address 0] which is an
integral multiple of the 'sizeof'.

Pretending that there is an array of a particular object type spanning
from address 0 all the way through our scratch space, it calculates
the index into such an array for the type of object of each operand.
That is, "for what 'index' does '(type *)0 + index' yield the lowest
pointer into our scratch space?"

It copies the values of the operands into.

It performs some extremely trivial (it's an example) arithmetic on
these copies. An interesting note is that:

::: The operations performed are according to the type of the macro's
operands. :::

It copies a result back into the "destination" object.

#include <stddef.h>
#include <stdio.h>

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_) \
do { \
/* Scratch space for copy of dest_ */ \
char dest_copy[sizeof (dest_) * 2]; \
/* Scratch space for copy of source_ */ \
char source_copy[sizeof (source_) * 2]; \
/* Offset of scratch space for copy of dest_ */ \
ptrdiff_t od = dest_copy - (char *)0; \
/* Offset of scratch space for copy of source_ */ \
ptrdiff_t os = source_copy - (char *)0; \
\
/* Evaluate dest_ and note its address */ \
void *dest = &(dest_); \
/* Evaluate source_ and note its address */ \
void *source = &(source_); \
/* Adjust offset for copy of dest_ */ \
od += od % sizeof (dest_); \
/* Adjust offset for copy of source_ */ \
os += os % sizeof (source_); \
/* Adjust offset for copy of dest_ to be an index */ \
od /= sizeof (dest_); \
/* Adjust offset for copy of source_ to be an index */ \
os /= sizeof (source_); \
/* Copy dest_ */ \
memcpy(TT(dest_) + od, dest, sizeof (dest_)); \
/* Copy source_ */ \
memcpy(TT(source_) + os, source, sizeof (source_)); \
\
/* At last, we can now work with our copies */ \
\
/* Increment copy of dest_, according to its type! */ \
TT(dest_)[od]++; \
/* Add 3 to copy of source_, according to its type! */ \
TT(source_)[os] += 3; \
/* Add the copies together and store in copy of dest_ */ \
TT(dest_)[od] += TT(source_)[os]; \
/* Copy back into dest_ */ \
memcpy(dest, TT(dest_) + od, sizeof (dest_)); \
} while (0)

int main(void) {
short s = 2;
int i = 3;
double f = 5.5;
long double Lf = 7.5;

destplus1_plus_sourceplus3(s, i);
destplus1_plus_sourceplus3(f, Lf);
printf("s: %hd ((2 + 1) + (3 + 3))\n", s);
printf("i: %i\n", i);
printf("f: %f ((5.5 + 1) + (7.5 + 3))\n", f);
printf("Lf: %Lf\n", Lf);

return 0;
}

I might expect that some bounds-checking implementations might not
enjoy adding _any_ integer to a null pointer, but at least the code
doesn't _dereference_ a null pointer. I am curious about which
implementations might produce unexpected results from this code for
that reason.

Enjoy (for what it's worth).

- Shao Miller
 
Ad

Advertisements

B

Barry Schwarz

Well here's another possibly curious/interesting use of the ternary
conditional operator.

The 'destplus1_plus_sourceplus3' macro takes two lvalues with any
type.

It creates "scratch space" for a copy of each object. The scratch
space for each is an array of 'char' that is twice the 'sizeof' the
object.

Somewhere within such a scratch space is a position which will be
properly aligned for the type of object, since the 'sizeof' the object
must be a multiple of the alignment of that type. Why? Because
otherwise an array of that type would produce objects that were
improperly aligned. That would be silly.

It calculates the position within the scratch space which is both
[properly aligned] and [a distance away from address 0] which is an
integral multiple of the 'sizeof'.

Pretending that there is an array of a particular object type spanning
from address 0 all the way through our scratch space, it calculates
the index into such an array for the type of object of each operand.
That is, "for what 'index' does '(type *)0 + index' yield the lowest
pointer into our scratch space?"

It copies the values of the operands into.

It performs some extremely trivial (it's an example) arithmetic on
these copies. An interesting note is that:

::: The operations performed are according to the type of the macro's
operands. :::

It copies a result back into the "destination" object.

#include <stddef.h>
#include <stdio.h>

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_) \
do { \
/* Scratch space for copy of dest_ */ \
char dest_copy[sizeof (dest_) * 2]; \
/* Scratch space for copy of source_ */ \
char source_copy[sizeof (source_) * 2]; \
/* Offset of scratch space for copy of dest_ */ \
ptrdiff_t od = dest_copy - (char *)0; \

This violates the requirements of 6.5.6-9 and therefore invokes
undefined behavior. Even if the compiler generates the expected code,
there is no guarantee that the result will fit in a ptrdiff_t (which
may be as small as 16 bits).
/* Offset of scratch space for copy of source_ */ \
ptrdiff_t os = source_copy - (char *)0; \
\
/* Evaluate dest_ and note its address */ \
void *dest = &(dest_); \
/* Evaluate source_ and note its address */ \
void *source = &(source_); \
/* Adjust offset for copy of dest_ */ \
od += od % sizeof (dest_); \
/* Adjust offset for copy of source_ */ \
os += os % sizeof (source_); \
/* Adjust offset for copy of dest_ to be an index */ \
od /= sizeof (dest_); \
/* Adjust offset for copy of source_ to be an index */ \
os /= sizeof (source_); \
/* Copy dest_ */ \
memcpy(TT(dest_) + od, dest, sizeof (dest_)); \

The first argument adds an integer to the expression (char*)0. This
violates 6.5.6-8 since you are not within the same array.

Even if the compiler generates the expected code, it appears that the
source and destination areas overlap. Overlapping areas produces
undefined behavior in memcpy. Maybe you should use memmove.
/* Copy source_ */ \
memcpy(TT(source_) + os, source, sizeof (source_)); \
\
/* At last, we can now work with our copies */ \
\
/* Increment copy of dest_, according to its type! */ \
TT(dest_)[od]++; \

This expression also violates 6.5.6-8.
/* Add 3 to copy of source_, according to its type! */ \
TT(source_)[os] += 3; \
/* Add the copies together and store in copy of dest_ */ \
TT(dest_)[od] += TT(source_)[os]; \

You are adding char but ignoring any carries.
/* Copy back into dest_ */ \
memcpy(dest, TT(dest_) + od, sizeof (dest_)); \
} while (0)

int main(void) {
short s = 2;
int i = 3;
double f = 5.5;
long double Lf = 7.5;

destplus1_plus_sourceplus3(s, i);
destplus1_plus_sourceplus3(f, Lf);
printf("s: %hd ((2 + 1) + (3 + 3))\n", s);
printf("i: %i\n", i);
printf("f: %f ((5.5 + 1) + (7.5 + 3))\n", f);
printf("Lf: %Lf\n", Lf);

return 0;
}

I might expect that some bounds-checking implementations might not
enjoy adding _any_ integer to a null pointer, but at least the code
doesn't _dereference_ a null pointer. I am curious about which
implementations might produce unexpected results from this code for
that reason.

Pick one:

Undefined behavior never produces unexpected results. Anything and
everything is expected

Since there are no expected results, all results from undefined
behavior are unexpected.
 
S

Shao Miller

Well here's another possibly curious/interesting use of the ternary
conditional operator.
The 'destplus1_plus_sourceplus3' macro takes two lvalues with any
type.
It creates "scratch space" for a copy of each object.  The scratch
space for each is an array of 'char' that is twice the 'sizeof' the
object.
Somewhere within such a scratch space is a position which will be
properly aligned for the type of object, since the 'sizeof' the object
must be a multiple of the alignment of that type.  Why?  Because
otherwise an array of that type would produce objects that were
improperly aligned.  That would be silly.
It calculates the position within the scratch space which is both
[properly aligned] and [a distance away from address 0] which is an
integral multiple of the 'sizeof'.
Pretending that there is an array of a particular object type spanning
from address 0 all the way through our scratch space, it calculates
the index into such an array for the type of object of each operand.
That is, "for what 'index' does '(type *)0 + index' yield the lowest
pointer into our scratch space?"
It copies the values of the operands into.
It performs some extremely trivial (it's an example) arithmetic on
these copies.  An interesting note is that:
::: The operations performed are according to the type of the macro's
operands. :::
It copies a result back into the "destination" object.
#include <stddef.h>
#include <stdio.h>
#define TT(x) (1 ? 0 : &(x))
#define destplus1_plus_sourceplus3(dest_, source_)              \
 do {                                                          \
   /* Scratch space for copy of dest_ */                       \
   char dest_copy[sizeof (dest_) * 2];                         \
   /* Scratch space for copy of source_ */                     \
   char source_copy[sizeof (source_) * 2];                     \
   /* Offset of scratch space for copy of dest_ */             \
   ptrdiff_t od = dest_copy - (char *)0;                       \

This violates the requirements of 6.5.6-9 and therefore invokes
undefined behavior.
Well it's tricky... Suppose we use 'char(*)[]' for the cast,
instead. The code is "pretending" that the Universe is an array of
'char'. We could make this pretense explicit by casting.

The "array object" in the point(s) you've referenced isn't defined to
specify whether it means _any_ array object or _a_particular_ array
object. I'm thinking of both "sub-arrays" and 'struct's, in
particular. I believe that it's precisely vague enough to allow for
the demonstration code's use.

Consider an object or objects with "allocated" storage duration.
"Array object" could only be the effective type for the accesses,
since there's no declared type. We may treat such a space any way we
like, essentially. We are allowed to 'memcpy' such objects and treat
them as an array of 'char' (for copying ourselves, for example). But
what we might really have (if such a thing makes sense) is an array of
'struct's. Copying them would thus copy "holes" (unspecified values
in unspecified padding) but we are permitted to do so by treating the
space as an array of 'char'.

This code _does_ assume that we are allowed to treat all of memory as
an array of 'char'. It doesn't even try to read/write to address 0 (a
null pointer). It doesn't even try to read/write any values in
between address 0 and the "scratch spaces." 6.2.6.1,p4 and p5.
 Even if the compiler generates the expected code,
there is no guarantee that the result will fit in a ptrdiff_t (which
may be as small as 16 bits).
Aha. A very good point. 'ptrdiff_t' puts an upper bound on pointer
subtraction for any contiguous range of memory. That is, if you have
an array of 'char' that is large enough, a pointer to its first
element subtracted from a pointer to its last element might not fit
within 'ptrdiff_t'. It'd be tough luck if that were the case here, as
we attempt to subtract a pointer to address 0 from a pointer to who-
knows-where.

So... We needn't use pointer subtraction for this calculation. We
can cast the address to the scratch space to something sufficiently
large and work with that type to calculate the index. Such would not
be hindered by 'ptrdiff_t'. Excellent catch, Barry!
The first argument adds an integer to the expression (char*)0.  This
violates  6.5.6-8 since you are not within the same array.  
Ugh. "Same array" is so vague by the standard. For memory with
"allocated" storage duration (via 'malloc', for example, in 6.5,p6),
it doesn't really have any meaning at all, otherwise we couldn't
perform pointer arithmetic within such memory at all.

Reading "between the lines," it would seem that there are some pretty
basic rules:
- You can attempt to access any memory you like, as any type you like
- If an access implies a "type" that is improperly aligned, well
that's a problem
- If an access tries to read a value that hasn't been explicitly set
by the implementation or the program, that's a problem
- If an access to memory which is not intended for use by the program
is attempted, that's a problem
- If an access to memory is as 'unsigned char', you can even read/
write data that you shouldn't use in any other way but for copying
(like padding)
Even if the compiler generates the expected code, it appears that the
source and destination areas overlap.  Overlapping areas produces
undefined behavior in memcpy.  Maybe you should use memmove.
Thanks, but the areas do not overlap. In fact, the example didn't
even need to use 'memcpy' at all. We can simply use assignment.

A sample visual:

0.........********.********........[....********....]..
[....********....]...

Address 0 at the far left. Who-knows-what following. First operand.
Unknown. Second operand. Unknown. Scratch space 1. Within the
scratch space is our copy, properly aligned and a nice, neat distance
away from 0. Same with the second scratch space.
   /* Copy source_ */                                          \
   memcpy(TT(source_) + os, source, sizeof (source_));         \
                                                               \
   /* At last, we can now work with our copies */              \
                                                               \
   /* Increment copy of dest_, according to its type! */       \
   TT(dest_)[od]++;                                            \

This expression also violates 6.5.6-8.
Same responses as above. I _did_ ask about bounds-checking
implementations which might have a problem, here. Do you know of any
which might not produce the desired results?
   /* Add 3 to copy of source_, according to its type! */      \
   TT(source_)[os] += 3;                                       \
   /* Add the copies together and store in copy of dest_ */    \
   TT(dest_)[od] += TT(source_)[os];                           \

You are adding char but ignoring any carries.
I don't follow you here, Barry. The code isn't adding anything of
type 'char'. 'TT(dest_)[od]' is an lvalue with the type of 'dest_'.
Same with the source. The demonstration code passes 'short' in the
first demo and 'double' in the second.
Pick one:

   Undefined behavior never produces unexpected results.  Anything and
everything is expected

   Since there are no expected results, all results from undefined
behavior are unexpected.
On the 'ptrdiff_t' limitation you've kindly pointed out, agreed. On a
bounds-checking implementation and adding an integer to a null
pointer, agreed. However, I still suspect that the adjusted code
below is highly portable (after picking an appropriate arithmetic type
large enough for the value of any pointer on a system). Thanks, Ben!

#include <stddef.h>
#include <stdio.h>

/*
* Pick an arithmetic type large enough to hold a value
* corresponding to any "address." Creativity required.
*/
typedef size_t addr_t;

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_) \
do { \
/* Scratch space for copy of dest_ */ \
char dest_copy[sizeof (dest_) * 2]; \
/* Scratch space for copy of source_ */ \
char source_copy[sizeof (source_) * 2]; \
/* Offset of scratch space for copy of dest_ */ \
addr_t od = (addr_t)dest_copy - 0; \
/* Offset of scratch space for copy of source_ */ \
addr_t os = (addr_t)source_copy - 0; \
\
/* Adjust offset for copy of dest_ */ \
od += od % sizeof (dest_); \
/* Adjust offset for copy of source_ */ \
os += os % sizeof (source_); \
/* Adjust offset for copy of dest_ to be an index */ \
od /= sizeof (dest_); \
/* Adjust offset for copy of source_ to be an index */ \
os /= sizeof (source_); \
/* Copy dest_ */ \
TT(dest_)[od] = dest_; \
/* Copy source_ */ \
TT(source_)[os] = source_; \
\
/* At last, we can now work with our copies */ \
\
/* Increment copy of dest_, according to its type! */ \
TT(dest_)[od]++; \
/* Add 3 to copy of source_, according to its type! */ \
TT(source_)[os] += 3; \
/* Add the copies together and store in copy of dest_ */ \
TT(dest_)[od] += TT(source_)[os]; \
/* Copy back into dest_ */ \
dest_ = TT(dest_)[od]; \
} while (0)

int main(void) {
int i = 3;
short s = 2;
long double Lf = 7.5;
double f = 5.5;

destplus1_plus_sourceplus3(i, s);
destplus1_plus_sourceplus3(Lf, f);
printf("s: %hd\n", s);
printf("i: %i ((3 + 1) + (2 + 3))\n", i);
printf("f: %f\n", f);
printf("Lf: %Lf ((7.5 + 1) + (5.5 + 3))\n", Lf);

return 0;
}
 
I

Ike Naar

typedef size_t addr_t;

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_) \
do { \
/* Scratch space for copy of dest_ */ \
char dest_copy[sizeof (dest_) * 2]; \
[snip]
/* Offset of scratch space for copy of dest_ */ \
addr_t od = (addr_t)dest_copy - 0; \

What's the purpose of subtracting zero from an integer value?
[snip]
/* Adjust offset for copy of dest_ */ \
od += od % sizeof (dest_); \

After this adjustment, od is not necessarily a multiple of
sizeof dest_ , if that's what you want. Let's take, for example,
od = 10013 and sizeof dest_ = 4 . The scratch space
spans from 10013 to 10020 (inclusive).
After your adjustment, od = 10013 + 10013 % 4 = 10013 + 1 = 10014 .
[snip]
/* Adjust offset for copy of dest_ to be an index */ \
od /= sizeof (dest_); \

and now od = 10014 / 4 = 2503 . Note that 4 * 2503 = 10012 which
is outside the scratch space.
/* Adjust offset for copy of source_ to be an index */ \
os /= sizeof (source_); \
/* Copy dest_ */ \
TT(dest_)[od] = dest_; \

Dereferencing null pointer (undefined behaviour). Even if the
implementation allows this, you're still clobbering memory you
don't own (clobbering 10012..10015, owning 10013..10020).
 
S

Shao Miller

Shao Miller   said:
typedef size_t addr_t;
#define TT(x) (1 ? 0 : &(x))
#define destplus1_plus_sourceplus3(dest_, source_)              \
 do {                                                          \
   /* Scratch space for copy of dest_ */                       \
   char dest_copy[sizeof (dest_) * 2];                         \
[snip]
   /* Offset of scratch space for copy of dest_ */             \
   addr_t od = (addr_t)dest_copy - 0;                          \

What's the purpose of subtracting zero from an integer value?
Ha! Nice catch! It is an incomplete erasure of the code of the
original post. :)
[snip]
   /* Adjust offset for copy of dest_ */                       \
   od += od % sizeof (dest_);                                  \

After this adjustment, od is not necessarily a multiple of
sizeof dest_ , if that's what you want. Let's take, for example,
od = 10013 and sizeof dest_ = 4 . The scratch space
spans from 10013 to 10020 (inclusive).
After your adjustment, od = 10013 + 10013 % 4 = 10013 + 1 = 10014 .
Yet another nice catch. :) The correct code would be:

od += sizeof (dest_) - od % sizeof (dest_);
[snip]
   /* Adjust offset for copy of dest_ to be an index */        \
   od /= sizeof (dest_);                                       \

and now od = 10014 / 4 = 2503 . Note that 4 * 2503 = 10012 which
is outside the scratch space.
Agreed.
   /* Adjust offset for copy of source_ to be an index */      \
   os /= sizeof (source_);                                     \
   /* Copy dest_ */                                            \
   TT(dest_)[od] = dest_;                                      \

Dereferencing null pointer (undefined behaviour). Even if the
implementation allows this, you're still clobbering memory you
don't own (clobbering 10012..10015, owning 10013..10020).
Hmmm... Why is this "dereferencing a null pointer"?

'TT(dest_)' is a null pointer with the type of '&(dest_)'. We add
'od' to that null pointer. Then we dereference the resulting
pointer. This is because of the equivalance of 'E1[E2]' to '(*((E1) +
(E2)))' (6.5.2.1,p2). If you are suggesting that adding an integer to
a null pointer is undefined behaviour by the Standard, then I agree.

Thus, from the original post:

"I might expect that some bounds-checking implementations might not
enjoy adding _any_ integer to a null pointer, but at least the code
doesn't _dereference_ a null pointer. I am curious about which
implementations might produce unexpected results from this code for
that reason."

Thanks, Ike. :) With your kind feedback, the corrected code follows:

#include <stddef.h>
#include <stdio.h>

/*
* Pick an arithmetic type large enough to hold a value
* corresponding to any "address."
*/
typedef size_t addr_t;

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_) \
do { \
/* Scratch space for copy of dest_ */ \
char dest_copy[sizeof (dest_) * 2]; \
/* Scratch space for copy of source_ */ \
char source_copy[sizeof (source_) * 2]; \
/* Offset of scratch space for copy of dest_ */ \
addr_t od = (addr_t)dest_copy; \
/* Offset of scratch space for copy of source_ */ \
addr_t os = (addr_t)source_copy; \
\
/* Adjust offset for copy of dest_ */ \
od += sizeof (dest_) - od % sizeof (dest_); \
/* Adjust offset for copy of source_ */ \
os += sizeof (source_) - os % sizeof (source_); \
/* Adjust offset for copy of dest_ to be an index */ \
od /= sizeof (dest_); \
/* Adjust offset for copy of source_ to be an index */ \
os /= sizeof (source_); \
/* Copy dest_ */ \
TT(dest_)[od] = dest_; \
/* Copy source_ */ \
TT(source_)[os] = source_; \
\
/* At last, we can now work with our copies */ \
\
/* Increment copy of dest_, according to its type! */ \
TT(dest_)[od]++; \
/* Add 3 to copy of source_, according to its type! */ \
TT(source_)[os] += 3; \
/* Add the copies together and store in copy of dest_ */ \
TT(dest_)[od] += TT(source_)[os]; \
/* Copy back into dest_ */ \
dest_ = TT(dest_)[od]; \
} while (0)

int main(void) {
int i = 3;
short s = 2;
long double Lf = 7.5;
double f = 5.5;

destplus1_plus_sourceplus3(i, s);
destplus1_plus_sourceplus3(Lf, f);
printf("s: %hd\n", s);
printf("i: %i ((3 + 1) + (2 + 3))\n", i);
printf("f: %f\n", f);
printf("Lf: %Lf ((7.5 + 1) + (5.5 + 3))\n", Lf);

return 0;
}

Do you or does anyone know of an implementation where one cannot add
an integer to a null pointer (of some type) and yield a valid pointer?

A fellow in another C-devoted forum suggested that C does not use a
flat memory model, so the very act of adding an integer to a null
pointer might not be as straight-forward as pretending that a null
pointer represents "the address 0". What do you think about that?
 
I

Ike Naar

Shao Miller   said:
TT(dest_)[od] = dest_;
Dereferencing null pointer (undefined behaviour). Even if the
implementation allows this, you're still clobbering memory you
don't own (clobbering 10012..10015, owning 10013..10020).
Hmmm... Why is this "dereferencing a null pointer"?

It isn't, sorry, my mistake; it's pointer arithmetic on a null pointer,
but that's also undefined.
 
Ad

Advertisements

N

Nick Keighley

Well here's another possibly curious/interesting use of the ternary
conditional operator.

The 'destplus1_plus_sourceplus3' macro takes two lvalues with any
type.

could you give a brief description of what your macro is actually
doing? It looks very large and complicated. Before I even *dreamed* of
putting a monster like that in my code I'd like to know what it is
for.

It creates "scratch space" for a copy of each object.  The scratch
space for each is an array of 'char' that is twice the 'sizeof' the
object.

Somewhere within such a scratch space is a position which will be
properly aligned for the type of object, since the 'sizeof' the object
must be a multiple of the alignment of that type.  Why?  Because
otherwise an array of that type would produce objects that were
improperly aligned.  That would be silly.

why can't alignment be larger than sizeof?

It calculates the position within the scratch space which is both
[properly aligned] and [a distance away from address 0] which is an
integral multiple of the 'sizeof'.

I'm not convinced this can be determined by a conforming C program.
Since the allocated block is only 2*sizeof that means that integeral
multiple is either 0 or 1?
Pretending that there is an array of a particular object type spanning
from address 0 all the way through our scratch space, it calculates
the index into such an array for the type of object of each operand.
That is, "for what 'index' does '(type *)0 + index' yield the lowest
pointer into our scratch space?"

It copies the values of the operands into.

It performs some extremely trivial (it's an example) arithmetic on
these copies.  An interesting note is that:

::: The operations performed are according to the type of the macro's
operands. :::

so it could operate on either ints or floats say? Why? Whta sort of
problems are you tackling where you don't know the type of the object
up front? Some sort of interpreter?

It copies a result back into the "destination" object.

#include <stddef.h>
#include <stdio.h>

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_)              \
  do {                                                          \
    /* Scratch space for copy of dest_ */                       \
    char dest_copy[sizeof (dest_) * 2];    

                                                                \
    /* Increment copy of dest_, according to its type! */       \
    TT(dest_)[od]++;  

  } while (0)

just a minute... I didn't see any use of the ternary operator! Ah your
TT macro uses the ternary operator.

#define TT(x) (1 ? 0 : &(x))

um. Doesn't this always evaluate to 0?
int main(void) {
  short s = 2;
  int i = 3;
  double f = 5.5;
  long double Lf = 7.5;

  destplus1_plus_sourceplus3(s, i);
  destplus1_plus_sourceplus3(f, Lf);
  printf("s: %hd ((2 + 1) + (3 + 3))\n", s);
  printf("i: %i\n", i);
  printf("f: %f ((5.5 + 1) + (7.5 + 3))\n", f);
  printf("Lf: %Lf\n", Lf);

  return 0;

}

I might expect that some bounds-checking implementations might not
enjoy adding _any_ integer to a null pointer, but at least the code
doesn't _dereference_ a null pointer.  I am curious about which
implementations might produce unexpected results from this code for
that reason.

Enjoy (for what it's worth).

what's the point?
 
N

Nick Keighley

Do you or does anyone know of an implementation where one cannot add
an integer to a null pointer (of some type) and yield a valid pointer?

it's undefined behaviour
A fellow in another C-devoted forum suggested that C does not use a
flat memory model,

no. C is not *required* to use a flat memory model.

so the very act of adding an integer to a null
pointer might not be as straight-forward as pretending that a null
pointer represents "the address 0".  What do you think about that?

apparently their have been implementaions where a null pointer
constant wasn't simply address zero.

I really don't see where you are going with all this.

Why the fixation of the null pointer?
 
I

Ike Naar

typedef size_t addr_t;

#define TT(x) (1 ? 0 : &(x))

#define destplus1_plus_sourceplus3(dest_, source_) \
do { \
/* Scratch space for copy of dest_ */ \
char dest_copy[sizeof (dest_) * 2]; \
/* Scratch space for copy of source_ */ \
char source_copy[sizeof (source_) * 2]; \
/* Offset of scratch space for copy of dest_ */ \
addr_t od = (addr_t)dest_copy; \
/* Offset of scratch space for copy of source_ */ \
addr_t os = (addr_t)source_copy; \
\
/* Adjust offset for copy of dest_ */ \
od += sizeof (dest_) - od % sizeof (dest_); \
/* Adjust offset for copy of source_ */ \
os += sizeof (source_) - os % sizeof (source_); \
/* Adjust offset for copy of dest_ to be an index */ \
od /= sizeof (dest_); \
/* Adjust offset for copy of source_ to be an index */ \
os /= sizeof (source_); \
/* Copy dest_ */ \
TT(dest_)[od] = dest_; \
/* Copy source_ */ \
TT(source_)[os] = source_; \
\
/* At last, we can now work with our copies */ \
\
/* Increment copy of dest_, according to its type! */ \
TT(dest_)[od]++; \
/* Add 3 to copy of source_, according to its type! */ \
TT(source_)[os] += 3; \
/* Add the copies together and store in copy of dest_ */ \
TT(dest_)[od] += TT(source_)[os]; \
/* Copy back into dest_ */ \
dest_ = TT(dest_)[od]; \
} while (0)

Is `` destplus1_plus_sourceplus3(d, s) '' supposed to
emulate `` d = (d + 1) + (s + 3); '' ?

If so, why not just use the latter?
 
S

Shao Miller

could you give a brief description of what your macro is actually
doing? It looks very large and complicated. Before I even *dreamed* of
putting a monster like that in my code I'd like to know what it is
for.
It's an _attempt_ to get something _akin_ to GCC's 'typeof' in ANSI
C. :) Much of the "monster" could be macro'd away into nice little
pieces. The demo code is just... A demo. Whatever reasons people
come up with for wanting 'typeof', I'd hope that _some_ of those
reasons might apply here.

Consider a common GCC macro using 'typeof':

#define foo(r_, x_, y_) \
do { \
typeof(x_) x = (x_); \
typeof(y_) y = (y_); \
/* Do stuff with x and y */ \
(r_) = whatever;
while (0)

This allows the macro to be useful given 'foo(result, ++a, b++);'
Such a use would _not_ be satisfied by the method of the demonstration
code.

But consider a case where you wish to force inline code. Or a case
where you don't wish to pull in an x-macro for every type (maybe
because you don't know them all). Or the case where you wish to do
something with copies of the operands but wish to leave the originals
alone entirely... Much like a function's parameters are copies...
Only these parameters can be any type.

C1X appears to have '_Generic()'. That might or might be because of a
perceived usefulness for type-generic programming in C.
why can't alignment be larger than sizeof?
Suppose your alignment is 8 bytes and 'sizeof' is 4. Now suppose you
have an array of two such objects. The first object needs to be
properly aligned with an address that's a multiple of 8. The next
object is 4 bytes after that... That leaves 4 remainder until the
next properly aligned address. The second element is misaligned. :)
'sizeof' thus cannot be a fractional multiple of the alignment
requirement.
It calculates the position within the scratch space which is both
[properly aligned] and [a distance away from address 0] which is an
integral multiple of the 'sizeof'.

I'm not convinced this can be determined by a conforming C program.
Since the allocated block is only 2*sizeof that means that integeral
multiple is either 0 or 1?
I would expect the multiple to be much more than that, typically.
'sizeof (x) * 2' gives us a scratch space. Within that scratch space,
there is a byte whose address is 'sizeof (x)' bytes away from address
0. That's an address that is an integral multiple of 'sizeof (x)', so
a whole number of pointer hops away from 0. Thanks to the 'sizeof'
and alignment relationship, it also must be aligned.

A case of an alignment requirement for an odd byte address with an
even 'sizeof' would be... An odd case. :)
so it could operate on either ints or floats say? Why? Whta sort of
problems are you tackling where you don't know the type of the object
up front? Some sort of interpreter?
Some of whatever floats 'typeof' users' boats. :)

At its pettiest, it's a way to forcibly inline code that works with
copies, as all functions do. Forced-inline functions, almost.
It copies a result back into the "destination" object.
#include <stddef.h>
#include <stdio.h>
#define TT(x) (1 ? 0 : &(x))
#define destplus1_plus_sourceplus3(dest_, source_)              \
  do {                                                          \
    /* Scratch space for copy of dest_ */                       \
    char dest_copy[sizeof (dest_) * 2];    

                                                                \
    /* Increment copy of dest_, according to its type! */       \
    TT(dest_)[od]++;  

  } while (0)

just a minute... I didn't see any use of the ternary operator! Ah your
TT macro uses the ternary operator.

#define TT(x) (1 ? 0 : &(x))

um. Doesn't this always evaluate to 0?
Excellent question. :) The magic here is that 'TT(x)' is not 0 at
all! It's a null pointer whose type is pointer-to-the-type-of-x.
Isn't that an interesting property?
what's the point?
See above.
 
S

Shao Miller

it's undefined behaviour
That term gets thrown around a lot, to be sure. :) I'll assume an
"undefined by omission" argument. Now let's take a predicate
approach.

A(x) : 'x' is an 'a'
B(x) : 'x' has a 'b'
C(x) : if A(x) and B(x), 'x' is a 'c'
D(x) : operation 'd' is defined if A(x)
E(x) : operation 'd' is undefined if C(x)

How about populating with:

x : something
a : pointer
b : null pointer value
c : null pointer
d : pointer arithmetic

By these, it is quite an assertion that the establishment of the class
of 'x' whose predicate 'C' is true necessitates explicit definition or
lack thereof for 'E(x)' to be true. That is, a null pointer is a
pointer. Pointer arithmetic is defined for pointers. Why should the
subset of pointers that are null pointers suddenly be excluded from
the definition for pointer arithmetic? If we find other subsets for
things, do those subsets then always require explicit definitions or
are they always "undefined behaviour" by omission? :)

But perhaps your reasoning is not the "undefined by omission" argument
at all. You didn't specify why it's undefined behaviour.
no. C is not *required* to use a flat memory model.
How can I know that as well as you know it? :) I currently don't
share that impression, but would be happy to learn.
apparently their have been implementaions where a null pointer
constant wasn't simply address zero.
Ok.

I really don't see where you are going with all this.
See the response post about 'typeof'.
Why the fixation of the null pointer?
_Any_ pointer would do. The code could be re-worked to use '(&(dest_)
+ od)' and we'd calculate 'od' to point into the scratch space. Null
pointer in this case simply demonstrates that interesting property of
the ternary conditional operator.
 
Ad

Advertisements

S

Shao Miller

Is `` destplus1_plus_sourceplus3(d, s) '' supposed to
emulate `` d = (d + 1) + (s + 3); '' ?

If so, why not just use the latter?
See the 'typeof' response post. It's just a demo. Forced-inline
"functions" (not actually functions) could be one possible use. Other
uses are possible, too.
 
B

Ben Bacarisse

Shao Miller said:
That term gets thrown around a lot, to be sure. :) I'll assume an
"undefined by omission" argument.

No, it's explicit:

"If both the pointer operand and the result point to elements of the
same array object, or one past the last element of the array object,
the evaluation shall not produce an overflow; otherwise, the behavior
is undefined."

<snip>
 
N

Nick Keighley

On Aug 3, 3:25 am, Nick Keighley <[email protected]>

pointer arithmatic applies to valid pointers and the null pointer
constant isn't a valid pointer.

From the ANSI Standard (I have it handy and I doubt there has been
substantial change in this area)

<quote>
3.3.6 Additive Operators
....
Constraints
....
o the left operand is a pointer to an object type and the right
operand has an integral type
</quote>

No other constraints deal with pointers. Since the null pointer
constant doesn't point to an object type there is no defined behaviour
in the case you specify

Now undefined behaviour

<quote>
1.6 Definition of Terms
....
o Undefine Behaviour - behaviour upon use of a nonportable or
erroneous program construct, ... for which the standard imposes no
requirements...
</quote>

It's a tautology if the standard provides no definition of behaviour
then the behaviour is undefined.
That term gets thrown around a lot, to be sure. :)  I'll assume an
"undefined by omission" argument.  Now let's take a predicate
approach.

A(x) : 'x' is an 'a'
B(x) : 'x' has a 'b'
C(x) : if A(x) and B(x), 'x' is a 'c'
D(x) : operation 'd' is defined if A(x)
E(x) : operation 'd' is undefined if C(x)

can't say I like your predicate names. How about

isA, HasB, opdefD

E seem pointless and I didn't understand C at all
How about populating with:

x : something
a : pointer
b : null pointer value
c : null pointer
d : pointer arithmetic

are the little letters teh same as the big letters

let NPC be the Null Pointer Constant

isPointer(NPC)
NOT isobjectpointer(NPC)
opdef+(x) => isobjectPointer(x)

theorum:
NOT opdef+(NPC)

I'll leave you fill in the proof, but it seems clear enough

By these, it is quite an assertion that the establishment of the class
of 'x'
NPC?

whose predicate 'C' is true necessitates explicit definition or

I never got C
lack thereof for 'E(x)' to be true.  That is, a null pointer is a
pointer.  Pointer arithmetic is defined for pointers.

for pointers to objects. For a language lawyer you don't read very
carefully
 Why should the
subset of pointers that are null pointers suddenly be excluded from
the definition for pointer arithmetic?  If we find other subsets for
things, do those subsets then always require explicit definitions or
are they always "undefined behaviour" by omission? :)

But perhaps your reasoning is not the "undefined by omission" argument
at all.  You didn't specify why it's undefined behaviour.



How can I know that as well as you know it? :)

because the terms "flat memory model" never appear in the standard. Ok
that's not enough you'd have to define what you meant by flat memory
model. Probably something along the lines of

Flatness:
every address has a successor and the set of addresses is closed
under the operation get-successor

and that the C standard implied Flatness

I'm pretty sure we can show the the standard doesn't require flatness
(you can't reach one array from another). And I think you can run a
non-flat model on top of a flat model. Though you might need very
large addresses in the flat model.
 I currently don't
share that impression, but would be happy to learn.

the fact that C can be implemented on flat memories is a pointer that
C is not incompatible with a flat model
apparently [there] have been implementations where a null pointer
constant wasn't simply address zero.
Ok.

I really don't see where you are going with all this.

See the response post about 'typeof'.

where do I find that?

_Any_ pointer would do.  

well no actually. Certain arithmatic on some pointers may result in
undefined adding anything non-zero to a null pointer is undefined.

The code could be re-worked to use '(&(dest_)
+ od)' and we'd calculate 'od' to point into the scratch space.  Null
pointer in this case simply demonstrates that interesting property of
the ternary conditional operator.

*what* interesting property?
 
N

Nick Keighley

On Aug 3, 3:21 am, Nick Keighley <[email protected]>



It's an _attempt_ to get something _akin_ to GCC's 'typeof' in ANSI
C. :)

thankyou.

I'm one of those peope, who hated lectures who started writing a proof
on the board at the beginning of the lecture and the QED 45 minutes
later. I need to know where we're going.

[the macro] creates "scratch space" for a copy of each object.  The scratch
space for each is an array of 'char' that is twice the 'sizeof' the
object.
Somewhere within such a scratch space is a position which will be
properly aligned for the type of object, since the 'sizeof' the object
must be a multiple of the alignment of that type.  Why?  Because
otherwise an array of that type would produce objects that were
improperly aligned.  That would be silly.
why can't alignment be larger than sizeof?

Suppose your alignment is 8 bytes and 'sizeof' is 4.  Now suppose you
have an array of two such objects.  The first object needs to be
properly aligned with an address that's a multiple of 8.  The next
object is 4 bytes after that...

pad it? Probably not allowed. I think you're right the "stride" of the
array is equal to sizeof
 That leaves 4 remainder until the
next properly aligned address.  The second element is misaligned. :)
'sizeof' thus cannot be a fractional multiple of the alignment
requirement.
ok




Excellent question. :)  The magic here is that 'TT(x)' is not 0 at
all!  It's a null pointer whose type is pointer-to-the-type-of-x.
Isn't that an interesting property?

I'm beginning to glean what you're on about, You could have put it in
the first paragraph...


<snip>
 
S

Shao Miller

No, it's explicit:

  "If both the pointer operand and the result point to elements of the
  same array object, or one past the last element of the array object,
  the evaluation shall not produce an overflow; otherwise, the behavior
  is undefined."
Ah yes. The code does require us to pretend. From the original post:

"...Pretending that there is an array of a particular object type
spanning
from address 0 all the way through our scratch space, it calculates
the index into such an array..."

Now if an integer can be cast to a pointer type (6.3.2.3,p5) and is
implementation-defined, what's to prevent us from pretending the
Universe is an array of objects we'll never attempt to access?
"Object" is precisely defined in 3.14,p1, but: Exactly what
constitutes an array object in order for pointer arithmetic to be well-
defined? Is it declared type? Effective type? Imaginary type?
Because we can copy (thus read and modify) any object by treatment as
array of 'char', doesn't that give a deep hint that objects are what
you make of them?

My impression is that these are strong points undefined behaviour (non-
exhaustive):
- An operation accessing storage via a particular effective type at an
improperly aligned address
- An operation accessing storage that the program is not entitled to
access
- An operation reading storage via a particular effective type where
the value is a trap representation for that type
- An operation reading storage via a particular effective type where
the the contents were last modified by an operation via a different
effective type
- An operation modifying storage via a particular effective type where
that storage is read-only

Is this really oversimplifying? It could very well be. But what is
it exactly that prevents us from pretending (via effective type, for
instance) that everything is within a universal array object? If that
can be made clear, then it can certainly be clear that the pointer
arithmetic is out-of-bounds.

Take a bounds-checking implementation who actually pays attention to
overflow. How do they define the boundaries for objects? Does it
matter? Isn't all pointer arithmetic implementation-defined? Are
object boundaries precisely defined by the Standard?

I'd really enjoy reading any thoughts on the subject you might be kind
enough to share; any will be appreciated. Thanks, Ben. :)
 
Ad

Advertisements

S

Shao Miller

pointer arithmatic applies to valid pointers and the null pointer
constant isn't a valid pointer.
Which part of the ANSI C Standard states this? I don't see it.
From the ANSI Standard (I have it handy and I doubt there has been
substantial change in this area)
It's handy to have. :)
<quote>
3.3.6 Additive Operators
...
Constraints
...
   o the left operand is a pointer to an object type and the right
operand has an integral type
</quote>

No other constraints deal with pointers. Since the null pointer
constant doesn't point to an object type there is no defined behaviour
in the case you specify
Erm, the result of the 'TT' macro isn't a null pointer constant. It's
a null pointer. Also, a null pointer of type 'type *' points to an
object type if 'type' is an object type, doesn't it? The null pointer
compares unequal to any pointer to an object, but does that mean it
doesn't point to an object type?
Now undefined behaviour

<quote>
1.6 Definition of Terms
...
o Undefine Behaviour - behaviour upon use of a nonportable or
erroneous program construct, ... for which the standard imposes no
requirements...
</quote>

It's a tautology if the standard provides no definition of behaviour
then the behaviour is undefined.
Agreed.



can't say I like your predicate names. How about
Ok. :)
isA, HasB, opdefD

E seem pointless and I didn't understand C at all
Ok.



are the little letters teh same as the big letters
Nope. The little letters were subjects, the big letters were
predicates; properties/attributes to be either true or false,
effectively providing a means of classification. Sorry to confuse.
let NPC be the Null Pointer Constant

  isPointer(NPC)
  NOT isobjectpointer(NPC)
  opdef+(x) => isobjectPointer(x)

theorum:
   NOT opdef+(NPC)

I'll leave you fill in the proof, but it seems clear enough
Ah, but 'TT' doesn't yield 'NPC'.
I never got C
Ok.


for pointers to objects. For a language lawyer you don't read very
carefully
I'm not sure about possessing that distinction, actually. :) I do try
to read carefully, but slip-ups and oversights are possible.
because the terms "flat memory model" never appear in the standard. Ok
that's not enough you'd have to define what you meant by flat memory
model. Probably something along the lines of
Aha. I guess I'm confused by the definition of casting an integer to
a pointer with an implementation-defined result. This brings flatness
to my mind.
Flatness:
   every address has a successor and the set of addresses is closed
under the operation get-successor
Nice. :)
and that the C standard implied Flatness
As above.
I'm pretty sure we can show the the standard doesn't require flatness
(you can't reach one array from another). And I think you can run a
non-flat model on top of a flat model. Though you might need very
large addresses in the flat model.
Well yes, the code does need to pretend we have an array large enough
to stretch from the null pointer (which is assumed to represent
address 0) through our scratch space. The original post mentions this
pretense.
the fact that C can be implemented on flat memories is a pointer that
C is not incompatible with a flat model
Ok. :)
so the very act of adding an integer to a null
pointer might not be as straight-forward as pretending that a null
pointer represents "the address 0".  What do you think about that?
apparently [there] have been implementations where a null pointer
constant wasn't simply address zero.
I really don't see where you are going with all this.
See the response post about 'typeof'.

where do I find that?
I see from another post that you found it. Great. :)
well no actually. Certain arithmatic on some pointers may result in
undefined adding anything non-zero to a null pointer is undefined.
Could you possibly share your thoughts on what pinpoints the
boundaries for objects, according to some C Standard? The pointer
arithmetic is well-defined based on some notion of "number of
elements" and "array object," but how are these precisely defined?
*what* interesting property?
I see from another post that you found it. :)

Thanks, Nick.
 
S

Shao Miller

... ... ...
thankyou.

I'm one of those peope, who hated lectures who started writing a proof
on the board at the beginning of the lecture and the QED 45 minutes
later. I need to know where we're going.
Sorry! This is good feedback. I can't reasonably expect anyone to
read my mind about the usefulness, can I? :)
pad it? Probably not allowed. I think you're right the "stride" of the
array is equal to sizeof
Agreed.



I'm beginning to glean what you're on about, You could have put it in
the first paragraph...
Sorry! Again, good feedback. Thanks, Nick.
 
B

Ben Bacarisse

Shao Miller said:
Ah yes. The code does require us to pretend. From the original post:

"...Pretending that there is an array of a particular object type
spanning
from address 0 all the way through our scratch space, it calculates
the index into such an array..."

Ah, well, there are lots of assumptions that make it defined. One of the
simplest is to assume that the implementation defines it (maybe in the
same way it defines the conversion of an integer to a pointer).
Undefined only mean undefined as far as the standard is concerned.
 
Ad

Advertisements

L

lawrence.jones

Nick Keighley said:
<quote>
3.3.6 Additive Operators
...
Constraints
...
o the left operand is a pointer to an object type and the right
operand has an integral type
</quote>

That constraint applies to pointer subtraction, not addition. The
corresponding constraint for addition is:

...one operand shall be a pointer to an object type and the
other shall have integer type.
No other constraints deal with pointers. Since the null pointer
constant doesn't point to an object type there is no defined behaviour
in the case you specify

But the case in question is for a null pointer, not a null pointer
constant. And most null pointers are, indeed, pointers to an object
type (only null function pointers aren't).

The undefined behavior comes from the later semantics that say:

If both the pointer operand and the result point to elements of
the same array object, or one past the last element of the array
object, the evaluation shall not produce an overflow; otherwise,
the behavior is undefined.

Since a null pointer does not point to any object, the "otherwise"
applies.
 
Ad

Advertisements


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

Top