0

I have a void* in plain C that I'm using while walking through some unstructured data, and I'd like to cast with dereference and autoincrement as I go. I would like to write the following:

void* ptr = /*something*/;
uint16_t i = *((uint16_t*) ptr)++;

The compiler objects and tells me "lvalue required as increment operand", but I guess I thought that a pointer cast as a pointer would still qualify as an lvalue.

Clearly my intent is for ptr to now point two bytes beyond where it pointed before. I can't remove the parentheses around the cast of ptr because ++ has higher precedence than the cast, so this won't work as I want it to:

int i = *(uint16_t*) ptr++;

I could of course do my own incrementing, like the following, but I was hoping for something elegant and concise:

int i = *(uint16_t) ptr;
ptr += sizeof(uint16_t);

What's a good way to do this?

Josh Sanford
  • 622
  • 5
  • 18
  • "I could of course..." - actually that is not possible, you cannot do arithmetic on void pointers – M.M Jan 19 '16 at 23:54
  • @M.M: Well, gcc does allow that as an extension (behaves like a `char *`). Which - of course - does not mean you should do it. – too honest for this site Jan 20 '16 at 00:01
  • If you have some octet stream of data and want to pull larger types from it (de-serialise), use shifts/masking. Don't rely on "problematic" features (without actual need). – too honest for this site Jan 20 '16 at 00:03
  • `*((uint16_t*) ptr)++;` ?? – chux - Reinstate Monica Jan 20 '16 at 00:05
  • in the interests of gore, `*(*(uint16_t **)&ptr)++` might appear to work, although it causes undefined behaviour due to aliasing violation – M.M Jan 20 '16 at 00:18
  • `((uint16_t*)ptr)++;` is like `((int)x)++;` where x might be 3.5. What would you expect it to do? – user253751 Jan 20 '16 at 01:06
  • @immibis, agreed on `((int)x)++`, but `((int*) x)++` where x is already a pointer would seem unambiguous to me. In the `int*` case on a platform where `sizeof(int)` is 4, I would expect x to point 4 bytes beyond where it previously pointed. But I do note @chqrlie's answer about this being disallowed. – Josh Sanford Jan 20 '16 at 13:32
  • @Olaf, yes, this is indeed a serialization/marshalling question, but I'm puzzled by your "shifts/masking" comment given that I'm not trying to use bitfields. May I ask what you had in mind? – Josh Sanford Jan 20 '16 at 13:34
  • Actually you want to pack/unpack wider types into/from octets (i.e. `uint8_t`). So, you have something like `uint8_t *p = ...; uint16_t v = ((uint16_t)p[0] << 8) | p[1]; p += 2;` (that is not complete/optimal, just to get the idea). Unpacking is similar. Actually explicit masking is not required, for unpacking just a cast to `uint8_t` on store. There is enough to be found already. The approach shown here is portable, type-safe and does not depend on endianess, etc.. – too honest for this site Jan 20 '16 at 14:00
  • Two very important rules: 1) In general you should not use `void *` unless absolutely justified. 2) Only cast iff(!) really necessary and you understand and accept **all** implications. – too honest for this site Jan 20 '16 at 14:04

3 Answers3

1

It would be simplest to write:

uint16_t *uptr = ptr;
uint16_t i = *uptr++;
ptr = uptr;            // if required

You might be able to restructure the larger code to have a uint16_t * as much as possible, removing the need for so many conversions.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • I think this is another (de)serialisation/marshalling question. I really wonder what is so complicated about doing it correctly. – too honest for this site Jan 20 '16 at 00:04
  • @M.M, thanks. I regret that I don't yet have enough reputation to upvote your answer. However, `uint16_t*` was simply used as an example, and is not necessarily representative of the typical case for the data. – Josh Sanford Jan 20 '16 at 13:47
  • @Olaf: "I really wonder what is so complicated about doing it correctly." Lack of experience? ;-) To be honest, I don't do a lot of serialization, and when I do, it's typically with well structured data that I have defined. This is a case where I just wanted to perform some simple operations and move on. – Josh Sanford Jan 20 '16 at 13:51
  • @JoshSanford: Maybe I'm just too deep into that stuff. I should change to 3D graphics programming or so;-) But there are really just two small & generic functions required (unless you have some exotic system without 8 bit bytes): one per direction. – too honest for this site Jan 20 '16 at 14:07
0

Incrementing a pointer cast to a different type has been disallowed for a long time, probably since C89. The reason is that the conversion from one type of pointer to another type can change the representation, and thus might not refer to the same object.

You must split the expression into 2 statements:

void *ptr = /*something*/;
uint16_t i = *(uint16_t*)ptr;
ptr = (uint16_t*)ptr + 1;

Or if context commands for a single expression, you could use this hack:

uint16_t i = ((uint16_t*)(ptr = (uint16_t*)ptr + 1))[-1];

Or this one if you want to obfuscate even more:

uint16_t i = (-1)[(uint16_t*)(ptr = (uint16_t*)ptr + 1)];
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Thanks. Your explanation led me to http://stackoverflow.com/q/5365153, which explained that the result of a cast is not an lvalue (specifically in answer http://stackoverflow.com/a/5381734). – Josh Sanford Jan 20 '16 at 13:44
0
  void *ptr;
  *(*(uint16_t **)&ptr)++;

Taking the address to the pointer (a void ** type) casting it to uint16_t **, dereference it, now you have a valid lvalue you can increment and dereference normally.

Marcelo Pacheco
  • 152
  • 1
  • 5