E
Eric Sosman
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.
If by "the good old days" you mean "before the ANSI Standard
in 1989," you might be sort of right. In those days there was no
formal definition of C, and all manner of compilers bestowed the
"C" label on themselves. Some of those may have had a void* type
(K&R C did not), and some of those may have permitted arithmetic
on that type. I never ran across a pre-ANSI compiler with these
characteristics, but there were lots and lots of compilers I
never ran across at all.
Also, if it's pre-ANSI C you speak of, I dispute the adjective
"good."
Starting with ANSI C, which first (formally) introduced void*
to the language, no conforming compiler has ever permitted
arithmetic on void* values. The only compiler I happen to know
of that permits it is gcc (in a non-conforming mode), but perhaps
there are others. There are certainly "self-respecting" compilers
that follow the language definition and forbid it.
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.
It'd be bogus. Since void is an incomplete type, the sizeof
the thing a void* points at is unknown. That's what makes void*
useful in the first place: You can point it at anything at all
without worrying about the sizeof the target. The very same void*
variable can point at a char, an int, and a struct foobar, all
during a single execution of one program. What is "the" size of
all these different targets?
Here's another incomplete type, and an example of how it
might be used in a hypothetical implementation of an ADT:
struct unknown; // incomplete type
struct unknown * newUnknown(int); // a "constructor"
struct unknown * uptr = newUnknown(42); // instantiation
Okay, uptr (we'll suppose) now points at a struct unknown instance.
If you now do ++uptr, what ought to happen? Should the address
advance by one byte? Forty-two bytes? Eleven? Where is the
"one past the end" location for the struct unknown object whose
size is a mystery? Arithmetic on a void* encounters exactly the
same problem: The size of the target is unknown, so the compiler
can't know how far to advance the pointer.
Ok, fine. So I adjusted, I would do this:
void *buf;
int n;
(char *)buf += n;
This is also bogus, for the same reason (double)n += 0.42
would be bogus. The left-hand side is an expression with an
operand, an operator, and a value, not an object to which you
can assign something (I'm speaking somewhat loosely here; look
up the term "assignable lvalue" for a more rigorous treatment).
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.)
Because the cast is an operator that derives a value from
its operand. Try this one: (char*)(ptr + 42) = "Hello". Where
do you think the pointer to 'H' should be stored, and why?
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?
buf = (char*)buf + n;
And before you say "That's just dim-witted," ponder
buf = (int*)buf + n;
It seems to me there are two misconceptions buried in your
mental model of the language. One is the notion that a pointer
is "just an address," which is wrong: A pointer is an address
*and* a type, and the type is important. The second mistake is
to think of the cast operator as meaning "let's pretend this
thing is of a different type," when in fact it means "take the
value of this thing, convert it to a new type, and deliver the
converted value (which may even be unequal to the original)."
You are not the first to fall victim to these two mistaken
notions, nor will you be the last.