why is arithmetic on (void*) so scary?

S

Stephan Beal

Hi, all!

Today i decided to turn on gcc's -pedantic flag on some of my code,
and i was rewarded with this:

whio_dev_mem.c:315: error: pointer of type ‘void *’ used in arithmetic

The offending code is part of an i/o device interface where the only
option for this particular bit (and several others like it) is to add
offsets to (void*):

memset( b + mb->alloced, 0, alen - mb->alloced );

In this particular case, i've realloc()ed memory to a larger size and
i'm zeroing out the new bytes. In some cases i have loops where i'm
having to iterate over a large input set (provided by a (void*) +
length info) and send the data via a smaller buffer. In those cases i
need to add to the input (void*) to get my current read/write offset.

i remember getting the same warning (but not an error) when compiling
this code on the SunStudio compiler (but without any special flags).
Since two compilers are bitching at me about this...

My question is: is it strictly illegal to add to a (void*) like this?

If it is illegal, strictly speaking, is there an alternative? Would it
instead be portable/legal to cast the (void*) to (unsigned char*) and
do the addition on that result?

My current workaround is to define a macro:

#if 0 /* enable this if you want to build with -pedantic. */
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((intptr_t)VP+PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((intptr_t)VP
+PLUS))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
# define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

and change the offending calls to use this instead of directly doing
(void*) addition.

Your insights and suggestions for improvements would be greatly
appreciated!

:-?
 
U

user923005

Hi, all!

Today i decided to turn on gcc's -pedantic flag on some of my code,
and i was rewarded with this:

whio_dev_mem.c:315: error: pointer of type ‘void *’ used in arithmetic

The offending code is part of an i/o device interface where the only
option for this particular bit (and several others like it) is to add
offsets to (void*):

            memset( b + mb->alloced, 0, alen - mb->alloced );

In this particular case, i've realloc()ed memory to a larger size and
i'm zeroing out the new bytes. In some cases i have loops where i'm
having to iterate over a large input set (provided by a (void*) +
length info) and send the data via a smaller buffer. In those cases i
need to add to the input (void*) to get my current read/write offset.

i remember getting the same warning (but not an error) when compiling
this code on the SunStudio compiler (but without any special flags).
Since two compilers are bitching at me about this...

My question is: is it strictly illegal to add to a (void*) like this?

If it is illegal, strictly speaking, is there an alternative? Would it
instead be portable/legal to cast the (void*) to (unsigned char*) and
do the addition on that result?

My current workaround is to define a macro:

#if 0 /* enable this if you want to build with -pedantic. */
#  define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((intptr_t)VP+PLUS))
#  define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((intptr_t)VP
+PLUS))
#else
#  define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
#  define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

and change the offending calls to use this instead of directly doing
(void*) addition.

Your insights and suggestions for improvements would be greatly
appreciated!

:-?

A void pointer does not have a stride. To assume one is a programming
error (read: '*You* blew it.').

If you know the true underlying type, then cast to that type (or
assign to an instance of that pointer type) and then do the work.

One reason for a void pointer is to prevent people who are not
supposed to be fiddling with an interface from attempting to do it.
Because you do not know the pointer's underlying object size, you
cannot safely manipulate it.

Assuming that a void pointer is an intptr_t is absurd, unless that is
what it happens to be, which is highly unlikely.

If (for instance) the underlying type is actually character data, then
the correct stride is 1. If (on the other hand), it is double, then
the size of the stride is sizeof(double). If (on the other hand), the
underlying type is struct foo, then the size of the stride is sizeof
struct foo.

Do you now understand why your compiler is shouting at you?
 
B

Ben Pfaff

Stephan Beal said:
My question is: is it strictly illegal to add to a (void*) like this?
Yes.

If it is illegal, strictly speaking, is there an alternative? Would it
instead be portable/legal to cast the (void*) to (unsigned char*) and
do the addition on that result?

Yes, that's fine.
 
U

user923005

Yes, that's fine.

Of course, it won't be a great idea if the underlying type is not
actually character.
I know that Ben knows this, but I am not sure if the OP does.
 
S

Stephan Beal

Of course, it won't be a great idea if the underlying type is not
actually character.
I know that Ben knows this, but I am not sure if the OP does.

In this case it's a device-agnostic i/o API, very similar to the
conventional fwrite(), fread() and friends (home page:
http://fossil.wanderinghorse.net/repos/whio/). The buffers are, for
all intents and purpose, as opaque as those given to fwrite() and fread
(). For purposes of reading/writing at this level, the stride must be
some known amount (one in the generic case), and we must rely on the
user to give us memory buffers of the proper sizes for their read/
write calls. (Since i'm the only client of the lib, i make sure the
lib isn't abused.;)

So far, the cast to (unsigned char *) sounds like the safest approach
for working around the problem, so i've adjusted the macros i showed
in the first post to:

#if 1 /* enable this if you want to build with -pedantic. */
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((char const *)VP+PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((unsigned char
const *)VP+PLUS))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
# define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

From what i've read (though i don't remember where), casting to
(unsigned char *) is guaranteed (or perhaps has a special concession?)
to behave how i'd like it to here.


Thank you both for your feedback :).
 
S

Spiros Bousbouras

So far, the cast to (unsigned char *) sounds like the safest approach
for working around the problem, so i've adjusted the macros i showed
in the first post to:

Your original approach namely

#if 0 /* enable this if you want to build with -pedantic. */
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((intptr_t)VP+PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((intptr_t)VP
+PLUS))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
# define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

is equally safe. But I don't think that C89 has intptr_t and it's
optional in C99 so what follows is more portable.
#if 1 /* enable this if you want to build with -pedantic. */
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((char const *)VP+PLUS))
^
unsigned
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((unsigned char const *)VP+PLUS))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
# define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

You might also want to put parentheses around VP and PLUS.
 
J

jameskuyper

Spiros said:
Your original approach namely

#if 0 /* enable this if you want to build with -pedantic. */
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((intptr_t)VP+PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((intptr_t)VP
+PLUS))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
# define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

is equally safe.

The standard allows you to convert a pointer to an intptr_t value and
back again; the resulting pointer is guaranteed to compare equal to
the original. The standard doesn't say anything useful about the
result of adding an integer to the intptr_t between the two
conversions; per 6.3.2.3p5, "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." This is not just
a pedantic quibble, there have been real systems where the result of
converting a point to an integer type that was sufficiently big for
the conversion to be reversible, produced an integer value for which
such arithmetic was meaningless, such as a number whose low bits
represented the segment number, and whose hight bits represented a
byte offset within the segment.

That doesn't sound "equally safe" to me.
But I don't think that C89 has intptr_t and it's
optional in C99 so what follows is more portable.


MUCH more portable!
 
S

Stephan Beal

Your original approach namely

#if 0 /* enable this if you want to build with -pedantic. */
#  define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((intptr_t)VP+PLUS))
#  define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((intptr_t)VP
+PLUS))
#else
#  define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
#  define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

is equally safe. But I don't think that C89 has intptr_t and it's
optional in C99 so what follows is more portable.

i'm targeting C99 only (i don't want the pain of missing variable-
sized arrays and stdint.h).i didn't realize intptr_t was optional,
though. Thanks for that tip.
                                                 ^
                                                unsigned

Doh, thanks!
You might also want to put parentheses around VP and PLUS.


Bonnie :).

Thanks again for the tips! -pedantic isn't complaining and the code
seems safe to me :).
 
K

Keith Thompson

Spiros Bousbouras said:
Your original approach namely

#if 0 /* enable this if you want to build with -pedantic. */
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((intptr_t)VP+PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((intptr_t)VP
+PLUS))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) (VP+PLUS)
# define WHIO_VOID_CPTR_ADD(VP,PLUS) (VP+PLUS)
#endif

is equally safe.

No, it's not. The standard guarantees a void* can be converted to
intptr_t and back to void*, yielding the original value. It does
*not* guarantee that arithmetic in the converted intptr_t value has
any particular relationship to pointer arithmetic, and in fact I've
worked on systems where it would fail.
But I don't think that C89 has intptr_t and it's
optional in C99 so what follows is more portable.


You might also want to put parentheses around VP and PLUS.

I wouldn't bother having two different versions of the code. (And it
doesn't matter here whether you use char or unsigned char.)

I'd either just write the code inline:

void *p = /*whatever */;
int offset = /*whatever*/
p = (char*)p + offset; /* the conversion back to void* is implicit */

or perhaps write a single version of the macros:

#define WHIO_VOID_PTR_ADD(VP, PLUS) ((void*)((char*)(VP) + (PLUS)))
#define WHIO_VOID_CPTR_ADD(VP, PLUS) ((void*)((const char*)(VP) + (PLUS)))

If you've got a correct and portable version of the code, why bother
writing and maintaining a second version that depends on a
non-portable extension?

Warning: The above code is untested.
 
A

Antoninus Twink

So far, the cast to (unsigned char *) sounds like the safest approach
for working around the problem

Why unsigned char * rather than plain old char *?

There's definitely a specific guarantee that void * and char * have the
same representation and are completely interchangeable. I don't know if
the same is true for void * and unsigned char *, though of course in the
real world there won't be a problem.
 
S

Spiros Bousbouras

or perhaps write a single version of the macros:

#define WHIO_VOID_PTR_ADD(VP, PLUS) ((void*)((char*)(VP) + (PLUS)))
#define WHIO_VOID_CPTR_ADD(VP, PLUS) ((void*)((const char*)(VP) + (PLUS)))
^
const
 
S

Spiros Bousbouras

Why unsigned char * rather than plain old char *?

Perhaps he might want to access the bytes at some point ?
There's definitely a specific guarantee that void * and char * have the
same representation and are completely interchangeable. I don't know if
the same is true for void * and unsigned char *, though of course in the
real world there won't be a problem.

Paragraph 15 of 6.2.5 says "The three types char, signed char, and
unsigned char are collectively called the character types" and
paragraph 27 says "A pointer to void shall have the same
representation and alignment requirements as a pointer to a
character type". So I would say that's a guarantee.
 
J

jameskuyper

Spiros said:
Perhaps he might want to access the bytes at some point ?

It's cast back to void* before any use can be made of it, so that
can't be a relevant issue.
 
S

Stephan Beal

or perhaps write a single version of the macros:

    #define WHIO_VOID_PTR_ADD(VP, PLUS) ((void*)((unsigned char*)(VP) + (PLUS)))
    #define WHIO_VOID_CPTR_ADD(VP, PLUS) ((void const *)((unsigned const char*)(VP) + (PLUS)))

If you've got a correct and portable version of the code, why bother
writing and maintaining a second version that depends on a
non-portable extension?

The problem is, some contexts are read() (where the client-supplied
dest buffer is non-const) and some are write() (where the client-
supplied src buffer is const). i could of course do:

#define WHIO_VOID_PTR_ADD(VP, PLUS,CN) ((void CN *)((unsigned char CN
*)(VP) + (PLUS)))

and then call it like WHIO_VOID_PTR_ADD(x,y,) for the non-const case,
but that's ugly as sin.

To sum up, the current implementation is:

#if 1
/*
Enable this if you want to build with -pedantic.

Thanks to the guys at:

http://groups.google.com/group/comp.lang.c/browse_thread/thread/fc1c48419f4d858d#

for their assistance on this.
*/
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((unsigned char const *)
(VP)+(PLUS)))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((unsigned char
const *)(VP)+(PLUS)))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((VP)+(PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((VP)+(PLUS))
#endif

but in all likelyhood i'll drop the second implementation altogether.

Thanks again to all of you - i've learned some important info today.
 
K

Keith Thompson

Stephan Beal said:
The problem is, some contexts are read() (where the client-supplied
dest buffer is non-const) and some are write() (where the client-
supplied src buffer is const). i could of course do:

#define WHIO_VOID_PTR_ADD(VP, PLUS,CN) ((void CN *)((unsigned char CN
*)(VP) + (PLUS)))

and then call it like WHIO_VOID_PTR_ADD(x,y,) for the non-const case,
but that's ugly as sin.

To sum up, the current implementation is:

#if 1
/*
Enable this if you want to build with -pedantic.

Thanks to the guys at:

http://groups.google.com/group/comp.lang.c/browse_thread/thread/fc1c48419f4d858d#

for their assistance on this.
*/
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((void*)((unsigned char const *)
(VP)+(PLUS)))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((void const*)((unsigned char
const *)(VP)+(PLUS)))
#else
# define WHIO_VOID_PTR_ADD(VP,PLUS) ((VP)+(PLUS))
# define WHIO_VOID_CPTR_ADD(VP,PLUS) ((VP)+(PLUS))
#endif

Sure, it makes sense to have one version for the const case and one
for the non-const case. I meant that there's no point in having
separate versions that use gcc's non-standard extension. Sorry if I
was unclear.
but in all likelyhood i'll drop the second implementation altogether.

Looks like we're in agreement.
 
S

Stephen Sprunk

Stephan said:
Hi, all!

Today i decided to turn on gcc's -pedantic flag on some of my code,
and i was rewarded with this:

whio_dev_mem.c:315: error: pointer of type ‘void *’ used in arithmetic
....
i remember getting the same warning (but not an error) when compiling
this code on the SunStudio compiler (but without any special flags).
Since two compilers are bitching at me about this...

My question is: is it strictly illegal to add to a (void*) like this?

It's a constraint violation.

GCC accepts void pointer arithmetic as an extension in its
non-conforming default mode; the -pedantic flag disables that extension
(and others) which is why you get the diagnostic. Apparently, SunStudio
even produces a diagnostic in its default mode, but then attempts to do
something (probably the same as GCC) with the code.

(A "warning" is typically about something that is questionable, but the
compiler will keep trying to produce code. An "error" is typically
something that prevents the compiler from continuing. However, the C
Standard does not distinguish between the two -- either is a "diagnostic".)
If it is illegal, strictly speaking, is there an alternative? Would it
instead be portable/legal to cast the (void*) to (unsigned char*) and
do the addition on that result?

If you're manipulating bytes, why not use a (unsigned char *) for the
pointer in the first place? That's what that type is for, and you can
freely assign back-and-forth to (void *) if required for some API.

S
 
S

Stephan Beal

If you're manipulating bytes, why not use a (unsigned char *) for the
pointer in the first place?  That's what that type is for, and you can
freely assign back-and-forth to (void *) if required for some API.

That's a good question and one i've contemplated. My i/o device lib is
modelled after conventional i/o APIs, e.g. read(2) and write(2) and
the C++ STL streams. i did experiment with using (uchar*) instead of
(void*). My initial testing was done by porting over code using fread
() and fwrite() (to abstract out the underlying storage), and in my
porting i found myself casting too much (i don't like to cast, and
will happily refactor if it saves me casting). So it became largely a
question of compatibility with conventional device-level i/o
interfaces. i figured that read() and write() have stood the test of
time, so it would be good to mimic them. Also, because the i/o lib is
generic and device-independent (e.g. it can wrap FILE pointers, file
descriptors, an auto-expanding memory buffer, or a static memory range
provided by the client) i felt that using void was more
philosophically correct than uchar.
 

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

Similar Threads

void * arithmetic 15
C99 Seg fault on while(), why ? 0
Arithmetic will null pointer 19
(void) pointer arithmetic 6
arithmetic on a void * pointer 140
Why is regex so slow? 21
pointer arithmetic question. 10
void pointers 36

Members online

Forum statistics

Threads
473,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top