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

are size_t and void* interchangeable?

535 views
Skip to first unread message

Maxim Yegorushkin

unread,
Feb 26, 2005, 12:50:42 AM2/26/05
to
Is it somehow guarantied by the standard that size_t has enough bits to
store a void* (which is guarantied to be able to store a pointer to any
type)? IOW, are size_t and void* safely interchangeable?

--
Maxim Yegorushkin

[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Carl Barron

unread,
Feb 26, 2005, 8:02:12 AM2/26/05
to
Maxim Yegorushkin <e-m...@yandex.ru> wrote:

> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?

No an std::size_t is an unsigned integral type capable of storing the
result of sizeof. A void * is a pointer that can represent a pointer to
anything. Integral types and pointer types are not interchangable even
their sizes are not guaranteed to be the same.

Ron Natalie

unread,
Feb 26, 2005, 10:18:36 AM2/26/05
to
Maxim Yegorushkin wrote:
> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?
>
Nope. There's no guarantee that there is any integral type that's
big enough to store an arbitrary pointer in. Size_t only needs to
be big enough to hold the size of a single maximally sized object
where void* may be larger.

size_t is probably going to fit the bill on most machines. I can't
think of any popular machine that it's not going to work on and it
would be odd to design a flat addressed machine otherwise. However,
I remember in the old banked machine days that the pointers could end
up being bigger than the size that any one object could be (since they
couldn't span banks).

Ben Hutchings

unread,
Feb 26, 2005, 10:24:49 AM2/26/05
to
Maxim Yegorushkin wrote:
> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?

This is generally true when the implementation uses a flat memory
model. However, if it uses a segmented memory model (e.g. x86 "real
mode") objects might be created within multiple segments but be
constrained to fit wholly within a segment. So size_t would need to
hold a segment offset, whereas void * would need to hold a segment ID
and a segment offset, and so would likely be larger. The standard
does not guarantee that any integer type is large enough to hold a
pointer value; that's implementation-defined.

--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.

Maxim Yegorushkin

unread,
Feb 27, 2005, 9:56:09 AM2/27/05
to
Maxim Yegorushkin wrote:

> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?

Thank you all for the answers.

So, what is the proper way of doing byte pointer arithmetics?

Example:

typedef struct { int x, y; } some;

Should one write:

#define FROM_Y_TO_SOME(y_ptr) \
(some*)((size_t)(y_ptr) - offsetof(some, y))

Or:

#define FROM_Y_TO_SOME(y_ptr) \
(some*)((char*)(y_ptr) - offsetof(some, y))

Antoun Kanawati

unread,
Feb 27, 2005, 11:39:23 AM2/27/05
to
Maxim Yegorushkin wrote:
> So, what is the proper way of doing byte pointer arithmetics?
>
> Example:
>
> typedef struct { int x, y; } some;
>
> Should one write:
>
> #define FROM_Y_TO_SOME(y_ptr) \
> (some*)((size_t)(y_ptr) - offsetof(some, y))
>
> Or:
>
> #define FROM_Y_TO_SOME(y_ptr) \
> (some*)((char*)(y_ptr) - offsetof(some, y))
>

The second form looks more appropriate, since you're dealing with
pointers only, instead of passing through an integer conversion
which stands a minor chance of being wrong.

--
A. Kanawati
NO.anto...@comcast.net

Dylan Nicholson

unread,
Feb 27, 2005, 8:07:51 PM2/27/05
to
Ron Natalie <r...@sensor.com> wrote in message news:<42206f36$0$5838$9a6e...@news.newshosting.com>...

> Maxim Yegorushkin wrote:
> > Is it somehow guarantied by the standard that size_t has enough bits to
> > store a void* (which is guarantied to be able to store a pointer to any
> > type)? IOW, are size_t and void* safely interchangeable?
> >
> Nope. There's no guarantee that there is any integral type that's
> big enough to store an arbitrary pointer in. Size_t only needs to
> be big enough to hold the size of a single maximally sized object
> where void* may be larger.
>
> size_t is probably going to fit the bill on most machines. I can't
> think of any popular machine that it's not going to work on and it
> would be odd to design a flat addressed machine otherwise.

On OS/400 (V5R3) sizeof(void*) == 16 and sizeof(size_t) == 4!
Even if you copy the contents of a pointer into a non-pointer object
that is big enough, and copy it back again, the pointer is not valid.

Andrew Koenig

unread,
Feb 28, 2005, 1:47:23 AM2/28/05
to
"Maxim Yegorushkin" <e-m...@yandex.ru> wrote in message
news:opsmrwl2j9ti5cme@wkcg6rirwp...

> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?

There is no guarantee that any integer type has enough bits to store a
pointer of any type.

Ben Hutchings

unread,
Feb 28, 2005, 3:14:19 PM2/28/05
to
Maxim Yegorushkin wrote:
> Maxim Yegorushkin wrote:
>
>> Is it somehow guarantied by the standard that size_t has enough bits to
>> store a void* (which is guarantied to be able to store a pointer to any
>> type)? IOW, are size_t and void* safely interchangeable?
>
> Thank you all for the answers.
>
> So, what is the proper way of doing byte pointer arithmetics?

Using byte (= char) pointers!

> Example:
>
> typedef struct { int x, y; } some;
>
> Should one write:
>
> #define FROM_Y_TO_SOME(y_ptr) \
> (some*)((size_t)(y_ptr) - offsetof(some, y))
>
> Or:
>
> #define FROM_Y_TO_SOME(y_ptr) \
> (some*)((char*)(y_ptr) - offsetof(some, y))

This latter definition is correct. By the way, note that offsetof is
only specified to be usable with POD class types, so you can't use
this technique with arbitrary classes.

--
Ben Hutchings
Having problems with C++ templates? Your questions may be answered by
<http://womble.decadentplace.org.uk/c++/template-faq.html>.

[ See http://www.gotw.ca/resources/clcm.htm for info about ]

Ron Natalie

unread,
Feb 28, 2005, 4:14:26 PM2/28/05
to
Dylan Nicholson wrote:
=

> On OS/400 (V5R3) sizeof(void*) == 16 and sizeof(size_t) == 4!
> Even if you copy the contents of a pointer into a non-pointer object
> that is big enough, and copy it back again, the pointer is not valid.

Then the compiler is NOT compliant. If there is an integral type
big enough, you should be allowed (although not advisable perhaps)
to convert from a pointer to that integral type and back to the
same type pointer (look up reinterpret cast).

Seungbeom Kim

unread,
Feb 28, 2005, 4:16:38 PM2/28/05
to
Dylan Nicholson wrote:

> Even if you copy the contents of a pointer into a non-pointer object
> that is big enough, and copy it back again, the pointer is not valid.

Are you sure?
5.2.10[expr.reinterpret.cast] says: (emphasis mine)

4 A pointer can be explicitly converted to any integral type large enough
to hold it.
5 A value of integral type or enumeration type can be explicitly
converted to a pointer. A pointer converted to an integer of sufficient
size (if any such exists on the implementation) and back to the same
pointer type *will have its original value*; mappings between pointers
and integers are otherwise implementation-defined.

The way I interpret this is that copying a pointer value to and from an
integer (yes, an integer, not an arbitrary object) that is big enough
does not take away its validity.

--
Seungbeom Kim

Dylan Nicholson

unread,
Mar 1, 2005, 5:55:16 AM3/1/05
to
Ron Natalie <r...@sensor.com> wrote in message news:<42231687$0$3171$9a6e...@news.newshosting.com>...

> Dylan Nicholson wrote:
> =
> > On OS/400 (V5R3) sizeof(void*) == 16 and sizeof(size_t) == 4!
> > Even if you copy the contents of a pointer into a non-pointer object
> > that is big enough, and copy it back again, the pointer is not valid.
>
> Then the compiler is NOT compliant. If there is an integral type
> big enough, you should be allowed (although not advisable perhaps)
> to convert from a pointer to that integral type and back to the
> same type pointer (look up reinterpret cast).
>
I didn't say it was an integral type - it was a struct. I'm not sure
there *is* an integral type large enough to hold a pointer.

Maxim Yegorushkin

unread,
Mar 2, 2005, 6:09:50 AM3/2/05
to
On 26 Feb 2005 00:50:42 -0500, Maxim Yegorushkin <e-m...@yandex.ru> wrote:

> Is it somehow guarantied by the standard that size_t has enough bits to
> store a void* (which is guarantied to be able to store a pointer to any
> type)? IOW, are size_t and void* safely interchangeable?

Actually, I was playing with an idea of stuffing double linked list
pointers with additional data, since the lower bits of the pointers are
not actually used due to alignment requirements. So, on a platform with
32-bit pointers a list node carries 4 bits of data, with 64-bit pointers -
6 bits. After quick optimization my profiler says that the stuffed list
append, prepend and remove operations are no more than 10% slower than
those of a plain list. I'm not sure about the benefits of using such a
list, I did it mostly as an experiment.

Here is what I came up with:

/*
stuffed list

The mechanics here are based on the premise that we can reuse pointer's
lower order bits
up to (log2(alignof(list_node*)) - 1) bit since they are always zero.
list_node has alignment at least as that of a pointer to it.
*/

#if defined __GNUC__
# define ST_LIST_ALIGNOF(x) __alignof__(x)
#elif defined _MSC_VER
# define ST_LIST_ALIGNOF(x) __alignof(x)
#else
# define ST_LIST_ALIGNOF(x) sizeof(x)
#endif

enum { st_list_data_bits = 2 * (
2 == ST_LIST_ALIGNOF(list_node*) ? 1
: 4 == ST_LIST_ALIGNOF(list_node*) ? 2
: 8 == ST_LIST_ALIGNOF(list_node*) ? 3
: 0
)
, st_list_ptr_mask = ~((1 << (st_list_data_bits / 2)) - 1)
};

typedef char st_list_compile_assert_1[st_list_data_bits != 0 ? 1 : -1];
typedef char st_list_compile_assert_2[sizeof(size_t) == sizeof(list_node*)
? 1 : -1];

#undef ST_LIST_ALIGNOF

typedef struct st_list_node
{
union { size_t prev; struct st_list_node
*align_prev_do_not_use_it_directly; };
union { size_t next; struct st_list_node
*align_next_do_not_use_it_directly; };
} st_list_node;

typedef struct { unsigned int data : st_list_data_bits; } st_list_data;

inline void st_list_init(st_list_node* node);
inline void st_list_set_data(st_list_node* node, st_list_data data);
inline st_list_data st_list_get_data(st_list_node const* node);
inline st_list_data st_list_strip_data(st_list_node* node);
inline st_list_node* st_list_prev(st_list_node* node);
inline st_list_node* st_list_next(st_list_node* node);
inline void st_list_append(st_list_node* prev, st_list_node* node);
inline void st_list_prepend(st_list_node* next, st_list_node* node);
inline void st_list_remove(st_list_node* node);
inline void st_list_swap(st_list_node* a, st_list_node* b);

void st_list_init(st_list_node* node)
{
node->align_prev_do_not_use_it_directly = node;
node->align_next_do_not_use_it_directly = node;
}

void st_list_set_data(st_list_node* node, st_list_data data)
{
/* node->prev takes high order data bits, node->next - lower order */

node->next = (node->next & st_list_ptr_mask) | (data.data &
~st_list_ptr_mask);

data.data >>= st_list_data_bits / 2;

node->prev = (node->prev & st_list_ptr_mask) | (data.data &
~st_list_ptr_mask);
}

st_list_data st_list_get_data(st_list_node const* node)
{
st_list_data data;

data.data = node->prev & ~st_list_ptr_mask;

data.data <<= st_list_data_bits / 2;

data.data |= node->next & ~st_list_ptr_mask;

return data;
}

st_list_data st_list_strip_data(st_list_node* node)
{
st_list_data data;

data.data = node->prev & ~st_list_ptr_mask;
node->prev &= st_list_ptr_mask;

data.data <<= st_list_data_bits / 2;

data.data |= node->next & ~st_list_ptr_mask;
node->next &= st_list_ptr_mask;

return data;
}

st_list_node* st_list_next(st_list_node* node)
{
return (st_list_node*)(node->next & st_list_ptr_mask);
}

st_list_node* st_list_prev(st_list_node* node)
{
return (st_list_node*)(node->prev & st_list_ptr_mask);
}

void st_list_append(st_list_node* prev, st_list_node* node)
{
size_t node_prev_data, node_next_data, prev_data, next_data;
st_list_node* next;

node_prev_data = node->prev & ~st_list_ptr_mask;
node_next_data = node->next & ~st_list_ptr_mask;

prev_data = prev->next & ~st_list_ptr_mask;
next = st_list_next(prev);
next_data = next->prev & ~st_list_ptr_mask;

node->prev = (size_t)prev | node_prev_data;
node->next = (size_t)next | node_next_data;
prev->next = (size_t)node | prev_data;
next->prev = (size_t)node | next_data;
}

void st_list_prepend(st_list_node* next, st_list_node* node)
{
size_t node_prev_data, node_next_data, prev_data, next_data;
st_list_node* prev;

node_prev_data = node->prev & ~st_list_ptr_mask;
node_next_data = node->next & ~st_list_ptr_mask;

next_data = next->prev & ~st_list_ptr_mask;
prev = st_list_prev(next);
prev_data = prev->next & ~st_list_ptr_mask;

node->prev = (size_t)prev | node_prev_data;
node->next = (size_t)next | node_next_data;
prev->next = (size_t)node | prev_data;
next->prev = (size_t)node | next_data;
}

void st_list_remove(st_list_node* node)
{
size_t node_prev_data, node_next_data, prev_data, next_data;
st_list_node *next, *prev;

node_prev_data = node->prev & ~st_list_ptr_mask;
prev = st_list_prev(node);
prev_data = prev->next & ~st_list_ptr_mask;

node_next_data = node->next & ~st_list_ptr_mask;
next = st_list_next(node);
next_data = next->prev & ~st_list_ptr_mask;

prev->next = (size_t)next | prev_data;
next->prev = (size_t)prev | next_data;

node->prev = (size_t)node | node_prev_data;
node->next = (size_t)node | node_next_data;
}

void st_list_swap(st_list_node* a, st_list_node* b)
{
st_list_node *a_prev, *b_prev;

a_prev = st_list_prev(a);
b_prev = st_list_prev(b);

st_list_remove(a);
st_list_remove(b);

if(a == b_prev) /* special case A<->B */
{
st_list_append(a_prev, b);
st_list_append(b, a);
}
else if(b == a_prev) /* special case B<->A */
{
st_list_append(b_prev, a);
st_list_append(a, b);
}
else /* general case A<-...->B or B<-...->A */
{
st_list_append(a_prev, b);
st_list_append(b_prev, a);

George Neuner

unread,
Mar 2, 2005, 10:32:59 PM3/2/05
to
On 28 Feb 2005 16:16:38 -0500, Seungbeom Kim <musi...@bawi.org>
wrote:

>Dylan Nicholson wrote:
>
>> Even if you copy the contents of a pointer into a non-pointer object
>> that is big enough, and copy it back again, the pointer is not valid.
>
>Are you sure?
>5.2.10[expr.reinterpret.cast] says: (emphasis mine)
>
>4 A pointer can be explicitly converted to any integral type large enough
>to hold it.
>5 A value of integral type or enumeration type can be explicitly
>converted to a pointer. A pointer converted to an integer of sufficient
>size (if any such exists on the implementation) and back to the same
>pointer type *will have its original value*; mappings between pointers
>and integers are otherwise implementation-defined.
>
>The way I interpret this is that copying a pointer value to and from an
>integer (yes, an integer, not an arbitrary object) that is big enough
>does not take away its validity.

That assumes there *is* an integral type large enough. That is not
the case on all platforms - older mainframe and mini architectures are
frequent offenders.

George
--
for email reply remove "/" from address

"Daniel Krügler (ne Spangenberg)"

unread,
Mar 3, 2005, 4:19:56 AM3/3/05
to
Hello George Neuner,

George Neuner schrieb:

>That assumes there *is* an integral type large enough. That is not
>the case on all platforms - older mainframe and mini architectures are
>frequent offenders.
>

It might be worth adding that even the new integer types of C99 capable
of holding object pointers,
namely intptr_t and uintptr_t, are **optional** types.

Greetings from Bremen,

Daniel Krügler

ka...@gabi-soft.fr

unread,
Mar 3, 2005, 9:15:31 AM3/3/05
to
Maxim Yegorushkin wrote:
> On 26 Feb 2005 00:50:42 -0500, Maxim Yegorushkin
> <e-m...@yandex.ru> wrote:

>> Is it somehow guarantied by the standard that size_t has
>> enough bits to store a void* (which is guarantied to be able
>> to store a pointer to any type)? IOW, are size_t and void*
>> safely interchangeable?

> Actually, I was playing with an idea of stuffing double linked
> list pointers with additional data, since the lower bits of
> the pointers are not actually used due to alignment
> requirements.

That's an old trick; I think it was used in some of the earliest
implementations of malloc (back in the 70's); the free space
arena was a linked list of blocks, with the low order bit of the
nextBlock pointer being used to indicate whether the block was
allocated or not. I'm sceptical of the value on a modern
machine (with virtual memory), but who knows; maybe you'll end
up with less cache misses because of a smaller memory
footprint:-).

For what it's worth, my usual solution in such cases was to
define something like:

#if HIBYTE1ST
static size_t const lowByteIndex = sizeof( void* ) + 1 ;
#else
static size_t const lowByteIndex = 0 ;
#endif

Then manipulate just the low byte with things like:

void* next ;
if ( ((char*)(&next)[ lowByteIndex ] & 1) != 0 ) ...

(I actually encapsulated the messiness in a few inline
functions: setFlag, resetFlag, testFlag. Or rather, #define's
-- I did this in the early 80's, and I'd not even heard of C++
then.)

Another solution would be to declare the pointers as char*, and
use ++ and -- to set and reset the bits (but you'd still need
the special logic for test).

> So, on a platform with 32-bit pointers a list node carries 4
> bits of data, with 64-bit pointers - 6 bits. After quick
> optimization my profiler says that the stuffed list append,
> prepend and remove operations are no more than 10% slower than
> those of a plain list. I'm not sure about the benefits of
> using such a list, I did it mostly as an experiment.

As I said, I'm sceptical of the benefits myself, but carefully
done, the run-time overhead isn't that much. The implementation
and maintenance overhead, on the other hand...

--
James Kanze GABI Software
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

0 new messages