-1

In the code which follows, I keep getting an error. How to modify the third line? Why's that keep happening? What's wrong?

#include <stdio.h> 
#include "stdlib.h"
#define ARRAY_IDX(type, array, i) ((type *)(array+i)) // you can only modify this line!

int main(int argc, const char * argv[]) {
    void *ptr = malloc(10*sizeof(int));
#ifdef ARRAY_IDX
    for (int i = 0; i < 10; i++) {
        ARRAY_IDX(int, ptr, i) = i * 2;
    }

    for (int i = 0; i < 10; i++) {
        printf("%d ", ARRAY_IDX(int, ptr, i));
    }
    free(ptr);
#else
    printf("Implement ARRAY_IDX first");
#endif
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 5
    "the error" --> which error? (http://ideone.com/gTOLSe) – Daniel Jour Aug 23 '16 at 06:16
  • 2
    In standard C compilers, you can't index a `void *` as your macro tries to do. GCC allows it by default, but GCC is not a standard C compiler by default. You're also missing an indirection operator in the macro. – Jonathan Leffler Aug 23 '16 at 06:18

2 Answers2

4

Looking at

ARRAY_IDX(int, ptr, i) = i * 2;

and

printf("%d ", ARRAY_IDX(int, ptr, i));

shows that the expression

ARRAY_IDX(int, whatever, whatever)

should expand into an expression of type int (and an lvalue, so that we can assign to it).

Starting off with a void * you first need to change (cast) it to a pointer that allows indexing, and since you want to index the elements of that array (not its individual bytes, which would be a violation of aliasing) you need to make it an int * first:

(int *)(ptr)

Now you have a pointer to an integer (array, hopefully). Increment it:

(int *)(ptr) + (idx)

Finally, you need an lvalue int expression. Dereference the pointer to get that:

(*((int *)(ptr) + (idx)))

Converting that to a preprocessor macro is something that should be doable, so I leave it up to you.


Note that whoever is giving you that code is - IMHO - not a teacher you should trust. This won't teach you much about correct C. It might teach you something about the preprocessor. But don't write such code. Just don't. Use correct types if possible. Check for failure of malloc.

Community
  • 1
  • 1
Daniel Jour
  • 15,896
  • 2
  • 36
  • 63
-2

There is nothing wrong with adding an int to a void pointer. For many years, compiler designers assumed that this was standard behavior, and it was implemented as such. It's every bit as standard as anonymous structs and unions, which compilers have had for almost 20 years and were only recently added in C11. Practically all compilers will compile this just fine without any warnings or errors, and without having to use any special compiler flags.

Your problem is, as I have pointed out, that you are assigning a value to a pointer. You need to dereference it after the cast.

#define ARRAY_IDX(type, array, i) ((type *)array)[i]
DeftlyHacked
  • 405
  • 2
  • 9
  • 2
    Still not defined fully. You are still indexing a `void *` which is not standard C. And you don't have enough parentheses. – Jonathan Leffler Aug 23 '16 at 06:25
  • @JonathanLeffler he's not indexing a void, he's casting to an int pointer and indexing that. – DeftlyHacked Aug 23 '16 at 06:27
  • 2
    Wrong! You've got `(array + i)` which is adding `i` (which is both the parameter name and the value passed as the argument to the macro) to a `void *` because the `ptr` in the code (passed as the `array` argument of the macro) is a `void *`. This is not allowed. – Jonathan Leffler Aug 23 '16 at 06:29
  • 1
    Cast to the proper type before adding `i` – Mateen Ulhaq Aug 23 '16 at 06:29
  • That's honestly the least of his problems. The biggest problem (which occurs twice in his code) is that he's trying to use a pointer like it's a value. – DeftlyHacked Aug 23 '16 at 06:30
  • 2
    @DeftlyHacked You cannot do pointer arithmetic on `void *` (there's no size ... so how many bytes should `+ 1` advance the pointer?) – Daniel Jour Aug 23 '16 at 06:32
  • 2
    @DeftlyHacked Better calm a bit down. You're just wrong: http://stackoverflow.com/a/3524270/1116364 – Daniel Jour Aug 23 '16 at 06:36
  • 4
    @DeftlyHacked: If you are using either GCC or Clang in GCC-compatibility mode, and you don't force it to behave with `-std=c11 -pedantic` or something similar, then you're right that GCC does allow you to index `void *` and it treats it as more or less equivalent to `char *`. However, standard C does not allow that. Worse, when you do index a `void *` with GCC, you are then incurring misaligned access — `ptr+1` is not correctly aligned for an `int`. It will give you more undefined behaviour. On RISC chips, you'll probably get a bus error. CISC chips might give you a result but it's wrong. – Jonathan Leffler Aug 23 '16 at 06:38
  • 1
    @DeftlyHacked Please don't try to edit my answer (and don't be rude). The approach you outline here is wrong, as shown by different people and sources (thus not just me). Therefore it must be corrected, in order to not confuse the OP or future readers. Being wrong happens and is no big deal, so please don't feel offended. – Daniel Jour Aug 23 '16 at 06:51
  • 1
    @Daniel Jour, I apologize. When I made my post, stack overflow rearranged all of the posts on the page and it looked like you altered my post. – DeftlyHacked Aug 23 '16 at 06:52
  • Note that ISO/IEC 9899:2011 §6.5.6 Additive operators says: _For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to a complete object type and the other shall have integer type._ A `void *` is not a pointer to a complete object type, and this is a constraint, so the C standard requires a diagnostic when you attempt to add an integer to a `void *`. The answer as amended with the diatribe about "there is nothing wrong with adding an int to a void pointer" is wrong — all standard-conforming C compilers are required to produce a diagnostic. – Jonathan Leffler Aug 23 '16 at 15:21
  • the n1256 draft stated: "6.5.6-2: For addition, either both operands shall have arithmetic type, or one operand shall be a pointer to an object type and the other shall have integer type." and "6.2.5-19: The void type comprises an empty set of values; it is an incomplete type that cannot be completed.". The original specs said nothing about the type being 'complete'. And technically speaking, an empty set is still a set, which is an object. – DeftlyHacked Aug 23 '16 at 16:31