2

In the following dummy example, I get a warning and I don't understand how I can get rid of it without explicitly define a pointer with int *_str = (int*)str:

#include <stdlib.h>
#include <stdint.h>

void foo(void *str, int c, size_t n) {
    for (size_t i = 0; i < n; i++)
        *((int*)str++) = c;
}

Here is what I get in gcc:

gcc -c -std=c99 -Wpointer-arith test.c
test.c: In function ‘mymemset’:
test.c:7:20: warning: wrong type argument to increment [-Wpointer-arith]
         *((int*)str++) = c;
                    ^

What's wrong with my code?

NOTE

I know this question is quite similar to this one, but it doesn't explain why I cannot do it and why I get an error.

Community
  • 1
  • 1
nowox
  • 25,978
  • 39
  • 143
  • 293
  • 2
    `*((int*)str++) = c;` --> `*((int*)(str)+i) = c;` – LPs Mar 17 '17 at 10:47
  • 4
    The `++` operator has higher precedence than the cast to `int *`. But *why* are you trying to stuff so much into so few characters? Are you competing in an obfuscated code contest? – Andrew Henle Mar 17 '17 at 10:49
  • @AndrewHenle I am curious and I think that using a third variable would give a more obfuscated code. – nowox Mar 17 '17 at 10:50
  • 1
    @nowox Having to count and match parenthesis while keeping track of operator precedence is a lot harder than clearly changing the variable type from `void *` to `int *` before operating on it. And I hope this version of `memset()` you're writing isn't supposed to be a direct replacement for the standard `memset()` - think about what happens if `sizeof(int)` isn't equal to 1. Then read the documentation for `memset()`. – Andrew Henle Mar 17 '17 at 10:54
  • 2
    I agree with @Andrew, just add `int* pInt = (int*)str;` before the loop and get rid of the cast. If you have to ask this question on SO, for *your own code*, then it's a pretty clear indicator the code is already obfuscated, and future maintainers will have even more problems. `*pInt++ = c;` is many times easier for a human to parse. – vgru Mar 17 '17 at 10:57
  • Btw, reinventing `memset` is a bad idea. Many C programmers realize they can shorten their loop times by assigning words instead of bytes, but these same programmers forget about alignment issues. – vgru Mar 17 '17 at 10:58
  • Note that the "fixed" code as suggested by LPs and others, violates the strict aliasing rule for some types of input buffer (e.g. `char buf[100];`) and also may be an alignment violation for some inputs – M.M Mar 17 '17 at 10:59
  • 2
    Possible duplicate of [-Wpedantic wrong type argument to increment after casting](http://stackoverflow.com/questions/26033338/wpedantic-wrong-type-argument-to-increment-after-casting) – vgru Mar 17 '17 at 11:00
  • @AndrewHenle I don't want to reinvent memset I just used this name to not have the remark "Why don't you use `int*` for the first argument. As I said I is a dummy function. – nowox Mar 17 '17 at 11:00
  • @Groo It is not a duplicate because the duplicate also mentioned on my question doesn't answer to my question. – nowox Mar 17 '17 at 11:01
  • @nowox: same principles still apply, violation of strict aliasing and issues with addresses which are not aligned to `int`. – vgru Mar 17 '17 at 11:01
  • @nowox the suggested duplicate does answer it, if you change `char` to `int`. I don't really think we need a separate question for each possible data type making this mistake.. – M.M Mar 17 '17 at 11:02
  • @nowox: to quote the answer: "Casting (void) dest and src to (...) *before* you use them gives the cleanest code". Just ignore the part which I placed in parentheses. – vgru Mar 17 '17 at 11:02

2 Answers2

1

str is a pointer to void. Since void is well, void, and has no size, how could the poor compiler possibly understand what you mean by str++?

str++ is supposed to make str point to the next object, but since the type void cannot be the type of any object str++ is meaningless.

Though you cast to int *, ++ is more "binding" (the word "precedence" confuses here) than the cast.

Malcolm McLean
  • 6,258
  • 1
  • 17
  • 18
AlexP
  • 4,370
  • 15
  • 15
0

You felt into a X-Y problem because you misunderstood what *((int*)str++) = c is supposed to do. So your question is not about void arithmetic, but a lvalue post-increment.

Let's put the void issue apart for a while and consider int64_t and int32_t which have a size of 8 and 4 on a 32-bit architecture byte addressable. In short, two types with a different size.

If you want to cast int32_t* into int64_t*, assign to it a value and post increment the pointer it, you may have written as follow (inspirated from your example):

int32_t* p = &value;
*((int64_t*)p++) = 42; 

According to the operator precedence the order execution is:

  1. Take p
  2. Save it for post-increment as a int32_t*
  3. Cast it to int64_t*
  4. Dereference it and assign 42 as a int64_t
  5. Post increment p

This example builds perfectly, but it is not doing what you think it does. What to want is to post-increment the cast pointer. So you need one more parenthesis:

*(((int64_t*)p)++) = 42; 

This time, you will get an error because ((int64_t*)p) is considered as a lvalue and thus it cannot be modified.

In this particular case you absolutely need a third variable to do the trick.

This is exactly the same with your void pointer.

nowox
  • 25,978
  • 39
  • 143
  • 293