arithmetic on a void * pointer

M

Mark Adler

Helpful C swamis,

In the good old days, any self-respecting C compiler knew what you
meant when you did arithmetic on a void * pointer, i.e. that the units
were bytes. Then someone decided that we would all be more productive
somehow if the compilers couldn't figure out obvious things like that
on their own. So now you can't do arithmetic on void * pointers.

Ok, fine. So I adjusted, I would do this:

void *buf;
int n;

(char *)buf += n;

But now I'm getting nastygrams like "warning: target of assignment not
really an lvalue; this will be a hard error in the future". Once again
someone, probably the same person, decided that compilers shouldn't
have to burden this great responsibility of understanding obvious
things like that. (And why a cast of a pointer to another pointer type
is no longer a pointer, I have no idea.)

So now what am I supposed to do? It starts to get just downright silly. E.g.

void *buf;
int n;
char *cmon;

cmon = (char *)buf;
cmon += n;
buf = (void *)cmon;

Is there a better way to work around this, at least until the *next*
dumbing down of the language?

Mark
 
K

Keith Thompson

Mark Adler said:
In the good old days, any self-respecting C compiler knew what you
meant when you did arithmetic on a void * pointer, i.e. that the units
were bytes. Then someone decided that we would all be more productive
somehow if the compilers couldn't figure out obvious things like that
on their own. So now you can't do arithmetic on void * pointers.

Arithmetic has *never* been permitted on void* by standard C. Prior
to the C89 standard, void* did not exist.

gcc does permit arithmetic on void* as an extension. It does so by
the kludge of pretending that sizeof(void) is 1.
Ok, fine. So I adjusted, I would do this:

void *buf;
int n;

(char *)buf += n;

But now I'm getting nastygrams like "warning: target of assignment not
really an lvalue; this will be a hard error in the future". Once
again someone, probably the same person, decided that compilers
shouldn't have to burden this great responsibility of understanding
obvious things like that. (And why a cast of a pointer to another
pointer type is no longer a pointer, I have no idea.)

Of course it's still a pointer; it's just not an lvalue. A pointer
cast results in a pointer value, not a pointer object. It's the same
with any other cast.

Similarly:

int n;
n = 42; /* ok */
(double)n = 42; /* nope */
(n + 1) = 42; /* nope */
So now what am I supposed to do? It starts to get just downright
silly. E.g.

void *buf;
int n;
char *cmon;

cmon = (char *)buf;
cmon += n;
buf = (void *)cmon;

Is there a better way to work around this, at least until the *next*
dumbing down of the language?

There as been no dumbing down of the language; this aspect hasn't
changed since void* was introduced by ANSI in 1989.

You can just use gcc with command-line options that don't tell it
to disable this extension.

If you want to advance a void* so it points n bytes later in memory,
you can do this:
buf = (char*)buf + n;
(note that the char* result is implicitly converted back to void*).
One advantage if this is that you can advance by n ints rather than n
chars if you want to.

Or, if you know you're going to be performing byte-oriented arithmetic
on a pointer object, you can just declare it as a char* (or unsigned
char*) in the first place.
 
D

Dennis \(Icarus\)

Mark Adler said:
Helpful C swamis,

In the good old days, any self-respecting C compiler knew what you meant
when you did arithmetic on a void * pointer, i.e. that the units were
bytes. Then someone decided that we would all be more productive somehow
if the compilers couldn't figure out obvious things like that on their
own. So now you can't do arithmetic on void * pointers.

Is void a data type?
Does void foo(); return a byte?
Does void a; define a byte?

Nope.
Ok, fine. So I adjusted, I would do this:

void *buf;
int n;

(char *)buf += n;

But now I'm getting nastygrams like "warning: target of assignment not
really an lvalue; this will be a hard error in the future". Once again
someone, probably the same person, decided that compilers shouldn't have
to burden this great responsibility of understanding obvious things like
that. (And why a cast of a pointer to another pointer type is no longer a
pointer, I have no idea.)

It is a pointer but an r-value not an l-value.
So now what am I supposed to do? It starts to get just downright silly.
E.g.

void *buf;
int n;
char *cmon;

cmon = (char *)buf;
cmon += n;
buf = (void *)cmon;

Is there a better way to work around this, at least until the *next*
dumbing down of the language?

Instead of using void * for buf, why not use char *?

Dennis
 
B

Ben Bacarisse

Mark Adler said:
In the good old days, any self-respecting C compiler knew what you
meant when you did arithmetic on a void * pointer, i.e. that the units
were bytes. Then someone decided that we would all be more productive
somehow if the compilers couldn't figure out obvious things like that
on their own. So now you can't do arithmetic on void * pointers.

I don't recall such a time but maybe I've forgotten. void (and hence
void *) came into C along with the restriction on doing arithmetic on
such pointers about 20 years ago. There was a short time when there
were unofficial compilers with void * support that could do what them
liked with them (because there was no standard) but as soon as C was
standardised, so was this (to me quite reasonable) restriction.
Ok, fine. So I adjusted, I would do this:

void *buf;
int n;

(char *)buf += n;

That seems like the wrong adjustment. If the buf is byte buffer, why
is the pointer not a char * to start with?
But now I'm getting nastygrams like "warning: target of assignment not
really an lvalue; this will be a hard error in the future". Once
again someone, probably the same person, decided that compilers
shouldn't have to burden this great responsibility of understanding
obvious things like that. (And why a cast of a pointer to another
pointer type is no longer a pointer, I have no idea.)

That's no what it is saying. It is saying that a cast expression is
not the kind of thing you can assign to (that's roughly what an lvalue
is). You might get a similar warning if you wrote +x = 42;
So now what am I supposed to do? It starts to get just downright silly. E.g.

You should write:

buf = (char *)buf + n;
void *buf;
int n;
char *cmon;

cmon = (char *)buf;
cmon += n;
buf = (void *)cmon;

That's overly complex.
Is there a better way to work around this, at least until the *next*
dumbing down of the language?

Personally, I like these rules. I think the smarten up the language
rather than dumb it down.
 
M

Morris Keesan

Helpful C swamis,

In the good old days, any self-respecting C compiler knew what you meant
when you did arithmetic on a void * pointer, i.e. that the units were
bytes. Then someone decided that we would all be more productive
somehow if the compilers couldn't figure out obvious things like that on
their own. So now you can't do arithmetic on void * pointers.

I've been programming in C since before the "void" type was invented, and I
don't think I've ever encountered a compiler that would do arithmetic on
(void *) objects. Since arithmetic on pointers is defined in terms of the
size of the object pointed to, why would you assume that sizeof(void) == 1?
Ok, fine. So I adjusted, I would do this:

void *buf;
int n;

(char *)buf += n;

Which has also never (in my experience) been legal. The result of a cast
expression is not something you can assign to, because it's a value
CONVERTED
TO A DIFFERENT TYPE, which in most cases one wouldn't expect to be a type
that's sufficiently compatible with the actual type of the original object
to make assignment reasonable.

There's a perfectly reasonable way to do this, which is

buf = (char *)buf + n;

which says exactly what you mean: "convert buf to a (char *), then add n
to that (char *), and assign the result back to buf".

This method has the advantage that it works equally well if the type
you're working with is anything OTHER than char, i.e. if (type *) is NOT
guaranteed
to have the same size and representation as (char *). E.g.

buf = (int *)buf + n;
or
buf = (struct foo *)buf + n;
 
M

Mark Adler

Arithmetic has *never* been permitted on void* by standard C.

I wasn't talking about the standard. I was talking about the compilers.
There as been no dumbing down of the language;

You're correct. I meant dumbing down of the compilers to agree with
the religious extremism of the standard.
If you want to advance a void* so it points n bytes later in memory,
you can do this:
buf = (char*)buf + n;

Thank you. That's what I'll do. Of course, at the loss of the
expessiveness and readability of the wonderful += operator of C.

Is void a data type?
Does void foo(); return a byte?
Does void a; define a byte?

Is void * a pointer? Yes.
If I add n to the register that pointer is in, does it go up by n? Yes.
Isn't that what you'd expect? Yes.

Anyway, I get the concept that void * deliberately makes the size of
the type ambiguous, so in principle arithmetic on it should be
undefined. On the other hand, the standard could make life easier and
still entirely self-consistent if it simply said that arithmetic on
void * pointers treats the size as 1. Why make a useful expression
illegal when instead it can mean what everyone expects it to mean?
It is a pointer but an r-value not an l-value.

Why is it not an l-value? I can see no ambiguity whatsoever about what
it means as an l-value.

Mark
 
D

Dennis \(Icarus\)

Mark Adler said:
I wasn't talking about the standard. I was talking about the compilers.

And compilers generally follow the standard. You may check to see if there's
a mode on te cpmpiler you're using that allows it as an extension.
Do you have an option like "disable language extensions" set?
You're correct. I meant dumbing down of the compilers to agree with the
religious extremism of the standard.

The C language is defined b the standard. Do you want to program in C or
not?
Thank you. That's what I'll do. Of course, at the loss of the
expessiveness and readability of the wonderful += operator of C.



Is void * a pointer? Yes.

Yes it is, and dereferencing it will cause problems.
If I add n to the register that pointer is in, does it go up by n? Yes.

That's assembly though - not C.
Isn't that what you'd expect? Yes.

If I was programming assembly, sure, but not if I was programming in C.
Anyway, I get the concept that void * deliberately makes the size of the
type ambiguous, so in principle arithmetic on it should be undefined. On
the other hand, the standard could make life easier and still entirely
self-consistent if it simply said that arithmetic on void * pointers
treats the size as 1. Why make a useful expression illegal when instead
it can mean what everyone expects it to mean?


Why is it not an l-value? I can see no ambiguity whatsoever about what it
means as an l-value.

Morris Keesan explains this nicely.

Dennis
 
M

Mark Adler

Which has also never (in my experience) been legal.

Until recently, gcc -Wall didn't complain about that.
which in most cases one wouldn't expect to be a type
that's sufficiently compatible with the actual type of the original object
to make assignment reasonable.

So what about the cases where assignment *is* reasonable? Like casting
a pointer?
This method has the advantage that it works equally well if the type
you're working with is anything OTHER than char, i.e. if (type *) is
NOT guaranteed
to have the same size and representation as (char *). E.g.

buf = (int *)buf + n;
or
buf = (struct foo *)buf + n;

I don't see why we should have to lose the += operator in this case.
(struct foo *)buf += n looks to me like it should make perfect sense to
the compiler.

Mark
 
M

Mark Adler

What is this in reference to?

Oh, I see -- another post here.

I wasn't convinced by the argument that because sometimes casting
results in something that's not an l-value, that you must then assume
that all casts cannot be an l-value.

Mark
 
M

Mark Adler

I've been programming in C since before the "void" type was invented, and I
don't think I've ever encountered a compiler that would do arithmetic on
(void *) objects.

My current copy of gcc (4.2.1) will, with all warnings enabled (-Wall)
is perfectly happy with (no warnings):

void *buf;
int n;

buf += n;

and generates code that does what apparently only an idiot like myself
would expect, which is to add n to the pointer address.

Mark
 
K

Keith Thompson

Mark Adler said:
Until recently, gcc -Wall didn't complain about that.

It sounds like gcc has been improved.
So what about the cases where assignment *is* reasonable? Like
casting a pointer?

You find it reasonable; I don't.

A cast specifies a conversion, which takes a value as its operand and
yields a converted value of the specified type.

An lvalue designates an object. For the result of a cast to designate
an object, there has to be some object *of the specified type* for it
to designate. Since the operand of the cast was of a different type,
there is no such object.

Conceivably the language could specify that a cast yields an lvalue if
the source and target types both have the same representation (this
happens to be guaranteed for char* and void*). But in my opinion that
would be ugly and inconsistent. Would you want this:

long l;
(int)l = 42;

to compile if and only if int and long happen to be the same size?
Would you want this:

int *ptr;
(void*)ptr = NULL;

to work as "expected" on most systems, but fail to compile on systems
where int* and void* have different representations?

And would you want such a special-case rule just so you can use "+="?
I don't see why we should have to lose the += operator in this case.
(struct foo *)buf += n looks to me like it should make perfect sense
to the compiler.

But it doesn't.
 
A

Alan Curry

void *buf;
int n;

(char *)buf += n;

You can fix that and keep your += operator:

*(char **)&buf += n;

Now there will be a large subthread arguing whether that's technically legal
(which must be what you wanted since you could have just declared buf as a
char * to begin with)

P.S.
I usually read usenet without paying much attention to who wrote which
article, trying to focus on content not personalities. But...

http://en.wikipedia.org/wiki/Mark_Adler? adler32, etc? and thinks void *
arithmetic should be acceptable? What a world!
 
M

Mark Adler

Since the operand of the cast was of a different type,
there is no such object.

Sez you. If what you said was correct, then we should never be able to
cast a pointer to another pointer type, since that object it's pointing
to doesn't exist.

Of course, in reality the object certainly does exist. There's a
reason the pointer to it was sent as a void *. The only reason I can
see for the void type is to have void * pointers (you can't have void
n), and the reason for void * pointers is to be able to carry a pointer
to any data while hiding and/or not caring what it is exactly. An
example is a custom memory allocator that needs to be passed the
structure it is using to manage the memory. The structure details are
hidden in the allocation code -- all the allocator needs is the pointer
and then it knows how to find the stuff that's there. (Pre-object
objects.)
Would you want this:

long l;
(int)l = 42;

No I would not want that. l is not a pointer.
int *ptr;
(void*)ptr = NULL;

to work as "expected" on most systems, but fail to compile on systems
where int* and void* have different representations?

Uh oh. Now I'm really worried, if having different, non-transferrable
data pointer representations is permitted by the C standard. I sure
hope you're wrong.

I certainly would expect the code above to work, though I can think of
no reason to do that since NULL is of the type void *, and so can be
assigned to other pointers without an explicit cast. It should just be
ptr = NULL. (At least that's what the compilers allow -- I hope that
doesn't get messed up too. I don't want to have to cast NULL every
time I use it.)

Anyway, I thought that the void * type *must* be able to be a vessel
for a pointer to any other data object. In fact, it has no other
reason to exist. If you cannot put an anydata * in a void * variable,
pass it along, and later convert it back to an anydata * and get the
anydata it's pointing to, then the value of void * is lost.

So is it not true that void * must be able to be a vessel for a pointer
to any data object?

Mark
 
I

Ian Collins

Mark said:
Sez you. If what you said was correct, then we should never be able to
cast a pointer to another pointer type, since that object it's pointing
to doesn't exist.

Of course, in reality the object certainly does exist. There's a reason
the pointer to it was sent as a void *. The only reason I can see for
the void type is to have void * pointers (you can't have void n), and
the reason for void * pointers is to be able to carry a pointer to any
data while hiding and/or not caring what it is exactly. An example is a
custom memory allocator that needs to be passed the structure it is
using to manage the memory. The structure details are hidden in the
allocation code -- all the allocator needs is the pointer and then it
knows how to find the stuff that's there. (Pre-object objects.)


No I would not want that. l is not a pointer.


Uh oh. Now I'm really worried, if having different, non-transferrable
data pointer representations is permitted by the C standard. I sure
hope you're wrong.

The two pointer types could have different sizes, provided sizeof(void*)
>= sizeof(int).

The problem above is the cast. Assignment would be fine.
Anyway, I thought that the void * type *must* be able to be a vessel for
a pointer to any other data object. In fact, it has no other reason to
exist. If you cannot put an anydata * in a void * variable, pass it
along, and later convert it back to an anydata * and get the anydata
it's pointing to, then the value of void * is lost.

So is it not true that void * must be able to be a vessel for a pointer
to any data object?

Yes, but that doesn't mean it has the same representation.
 
K

Keith Thompson

Mark Adler said:
Sez you. If what you said was correct, then we should never be able
to cast a pointer to another pointer type, since that object it's
pointing to doesn't exist.

Huh?

If you're going to snip context, can you please indicate that you're
doing so? Here's what I wrote:

An lvalue designates an object. For the result of a cast to
designate an object, there has to be some object *of the specified
type* for it to designate. Since the operand of the cast was of a
different type, there is no such object.

I'm not talking about the object (if any) to which the pointer points.
I'm talking about the pointer object itself.

For example, given:

void *vp = ...;
... (char*)vp ...

vp is an object of type void*. There is no object of type char*.
There is a *value* (not an lvalue) of type char*, the result of
converting the value of vp from void* to char*.

[...]
No I would not want that. l is not a pointer.

And why does that matter?
Uh oh. Now I'm really worried, if having different, non-transferrable
data pointer representations is permitted by the C standard. I sure
hope you're wrong.

I'm not. Different pointer types can have different representations.

Consider a system where a machine address points to, say, a
64-bit word, but the C implementation specifies an 8-bit byte.
Word pointers can be represented as machine addresses, but byte
pionters need an additional offset stored somewhere. (I've worked
on such a system.)

You might take a look at section 4 of the comp.lang.c FAQ,
I certainly would expect the code above to work, though I can think of
no reason to do that since NULL is of the type void *, and so can be
assigned to other pointers without an explicit cast. It should just
be ptr = NULL. (At least that's what the compilers allow -- I hope
that doesn't get messed up too. I don't want to have to cast NULL
every time I use it.)

I just used NULL as a trivial example. The point isn't whether it's a
sensible thing to do, it's whether it's legal.
Anyway, I thought that the void * type *must* be able to be a vessel
for a pointer to any other data object. In fact, it has no other
reason to exist. If you cannot put an anydata * in a void * variable,
pass it along, and later convert it back to an anydata * and get the
anydata it's pointing to, then the value of void * is lost.

So is it not true that void * must be able to be a vessel for a
pointer to any data object?

Depends on what you mean by "vessel".

A value of any pointer type (other than a pointer-to-function type)
may be converted to void* and back to the original type without
loss of information. The language specifically guarantees this.

But it's important to understand that conversions operate on values,
not on objects. There is no implication that an object of type void*
can be treated as if it were an object of some other pointer type
(except for the special guarantee that void* and char* have the
same representation). Conversion is not type-punning.

It's true that, on many systems, all pointer types have the same
representation, but the language does not make this assumption.

I think the use of the unadorned word "pointer" can lead to a great
deal of confusion. A pointer object and a pointer value are two
very different things.

It seems to me like you dislike the fact that C doesn't let you
treat pointer objects as if they were of different pointer types.
That's fine; there are certainly things I dislike about C (though
that's not one of them). But the language is the way it is.
You might even consider the possibility that there might be rational
reasons for for some of the decisions, even if you might not
agree with them.

And there are plenty of other languages -- including the C-like
dialect that gcc implements.
 
S

Seebs

Helpful C swamis,

In the good old days, any self-respecting C compiler knew what you
meant when you did arithmetic on a void * pointer, i.e. that the units
were bytes. Then someone decided that we would all be more productive
somehow if the compilers couldn't figure out obvious things like that
on their own. So now you can't do arithmetic on void * pointers.

I've never seen anything but gcc do that. There was no time ever when
it was part of the spec, and I think gcc was wrong.

If you want a byte pointer, use one.
Ok, fine. So I adjusted, I would do this:

void *buf;
int n;

(char *)buf += n;

But now I'm getting nastygrams like "warning: target of assignment not
really an lvalue; this will be a hard error in the future". Once again
someone, probably the same person, decided that compilers shouldn't
have to burden this great responsibility of understanding obvious
things like that. (And why a cast of a pointer to another pointer type
is no longer a pointer, I have no idea.)

It is a pointer, but it isn't an lvalue.

"(type) x" is an expression of the same class as "x + 1" -- it is a derived
value. Obviously, you know what is meant by:
x + 1 = 3;

but C doesn't work that way.
Is there a better way to work around this, at least until the *next*
dumbing down of the language?

Your premise is wrong; neither of the things you propose was ever standard,
and I don't think either of them makes sense.

If I really want to do this, I do:
v = ((char *) v) + 3;

.... Except I bet I don't, because if I want to do arithmetic on a pointer,
that's because I have a concept of what kind of things it points to. If
I really want to use a pointer-to-bytes, that's called "unsigned char *".
"void *" is for a typeless address, and since it has no type, there is no
meaningful size to increment it by when adding one...

-s
 
S

Seebs

You're correct. I meant dumbing down of the compilers to agree with
the religious extremism of the standard.

Could you name a single compiler other than gcc that actually accepted
this? I can't.
Is void * a pointer? Yes.
If I add n to the register that pointer is in, does it go up by n? Yes.
Isn't that what you'd expect? Yes.

Not really. I'd expect it to go up by n*sizeof(*ptr), and since I don't
know what size object it points to, I can't have an expectation
Why make a useful expression
illegal when instead it can mean what everyone expects it to mean?

First: Because not everyone expects that. Many people might, for
instance, expect "v += 3" to increment by three times the size of the
thing v points to. Whatever that is.

Second: Because in practice such code is nearly always buggy.
Why is it not an l-value? I can see no ambiguity whatsoever about what
it means as an l-value.

On some real-world systems, "(int *)" and "(void *)" have different
representations. It is not at all clear what you want.

int i = 3;

(double) i = 3.75;

What should i be?

In general, you can't meaningfully assign to a computed value, only to
an object.

-s
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top