15

I am allocating memory for an array, but I am moving where the pointer points forward a little. Accessing the elements works fine. It started to produce a problem with freeing the allocated memory though. Malloc complains that the pointer being freed was never allocated. The problem can reproduced with this simplified code:

int *pointer = malloc(sizeof(int)) + 1;
free(pointer - 1);

I started experimenting, and found this slight variation of the code to work.

int *pointer = malloc(sizeof(int));
pointer += 1;
free(pointer - 1);

What is the += doing different than just adding 1 to the pointer malloc returns in one line?

Znapi
  • 395
  • 1
  • 3
  • 11
  • 9
    `malloc(sizeof(int)) + 1` is typed-pointer math against a `void*` (which is utterly non-standard, btw). `pointer += 1;` is pointer math against a `int*` (which *is* standard). *Both* are using typed-pointer math in the `free` expression, the former is not the same address as the malloc result; the latter *is*. Casting the `malloc` result (which you should *never* do in C), to `((int *)malloc(sizeof(int))) + 1;` will garner synonymous results in both examples. – WhozCraig Jan 31 '15 at 20:49
  • 1
    It's worth mentioning that working outsize the bounds of the memory provided by `malloc()`/`calloc()`/`strdup()` is undefined behavior, and will usually cause major headaches down the line. While this is an interesting question, the reason your code has issues is because you're intentionally bringing about undefined behavior. – Cloud Jan 31 '15 at 20:59
  • 1
    @Dogbert The second variant is fully conforming as long as the OP never dereferences the pointer without offsetting it back again (i.e. `pointer[0]` is UB, `pointer[-1]` isn't), because of the one-past-the-end special case. – zwol Feb 01 '15 at 01:45
  • @WhozCraig: I'm puzzled by "Casting the malloc result (which you should never do in C)". Seems to me that's just backwards: you pretty much have to cast the (void *) returned by malloc to some other type in order to use it. – jamesqf Feb 01 '15 at 03:20
  • @jamesqf not in C. In C `void*` is implicitly cast to *any* data pointer type (and back). You can freely assign `void*` to any `type*` or `const type *` (though the latter makes little sense as a direct assignment from `malloc`). [**See here**](http://stackoverflow.com/questions/605845/do-i-cast-the-result-of-malloc) for more info. In C++ the cast is unavoidable, but in most circumstances it isn't an issue as there you're nearly always using `operator new` anyway. – WhozCraig Feb 01 '15 at 05:14
  • In C the cast is automatic, but no one stops you from writing a manual cast if you want to. The compiler won't complain. So 'should never do' is a bit strong? :) – Torp Feb 01 '15 at 10:38

3 Answers3

45

The return type of malloc is void *, which, according to the standard, cannot be used for pointer arithmetic.

There is a GNU extension, however, that treats sizeof(void) as 1 and that is why your first fragment compiles. Later on, pointer - 1 subtracts sizeof(int) and not 1, hence the pointer mismatch.

The actual offsets applied when doing pointer arithmetic always depend on the type of the pointer. Since pointer is an int *, the actual offsets will be multiplied by sizeof(int). If sizeof(void) is 1 or if it's a char * pointer (sizeof(char) == 1), no multiplication takes place.

Blagovest Buyukliev
  • 42,498
  • 14
  • 94
  • 130
  • Ok, so casting the return of malloc to an int* works. Why is arithmetic on void* not allowed? – Znapi Jan 31 '15 at 20:52
  • 3
    @Znapi because pointer arithmetics work based on the size of the type of the pointee, and the pointee of `void*` has no type by definition. – Quentin Jan 31 '15 at 20:53
  • 1
    Minor nitpick, but `sizeof(void)` is technically something that compiles in C, though it's not valid. It doesn't compile in C++. http://stackoverflow.com/questions/1666224/what-is-the-size-of-void – Cloud Jan 31 '15 at 20:53
  • @Znapi because the size of `void` is unknown. For `char*` the addition of `1` adds 1 to the pointer. For a (32-bit) `int*` the addition of `1` adds 4 to the pointer. – Weather Vane Jan 31 '15 at 20:54
  • @Quentin `sizeof(void*)` is defined and valid (changes from system to system, OS to OS, etc), but `sizeof(void)` isn't something that should find its way into production code, even if it does compile. – Cloud Jan 31 '15 at 20:57
  • @Dogbert absolutely true, but what does it have to do with my comment ? – Quentin Jan 31 '15 at 21:00
  • @Dogbert in MSVC `printf("%d", sizeof(void))` generates a compiler warning, and prints `0`. – Weather Vane Jan 31 '15 at 21:06
  • 1
    @WeatherVane probably because you are missing something important: `*`. That `sizeof(void)` is 0 is not surprising. – idmean Jan 31 '15 at 21:35
  • @idmean `sizeof(void*)` is the same as for all other pointers, but the type pointed to, is size 0, so pointer arithmetic for `void*` is useless, or undefined, except by a few compilers. – Weather Vane Jan 31 '15 at 22:53
  • 3
    @Dogbert: No, `sizeof (void)` is a constraint violation, requiring a diagnostic (which is as close as the standard comes to saying something is illegal). A conforming compiler *must* at least warn about it, and *may* (and IMHO should) reject it. The accepted answer in the linked question is incorrect. See [N1570](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf) 6.5.3.4p1, which forbids applying `sizeof` to an incomplete type. – Keith Thompson Feb 01 '15 at 03:04
5

Pointer arithmetic can be done using either of + or +=. It has nothing to do with the error produced.

malloc returns void * type. C standard says that:

C11: 6.2.5 Types (p19):

The void type comprises an empty set of values; it is an incomplete type that cannot be completed.

6.5.6 Additive operators (p2):

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. (Incrementing is equivalent to adding 1.)

void * is pointer to incomplete type. Therefore, no addition will be performed with void * type. That's why

int *pointer = malloc(sizeof(int)) + 1;  

is wrong and will lead to run time error on executing free(pointer - 1);.

Community
  • 1
  • 1
haccks
  • 104,019
  • 25
  • 176
  • 264
  • 1
    You misquoted the standard. 6.5.6 says "... or one operand shall be a pointer to a **complete** object type and the other shall have integer type." (emphasis added). C11 changed the definition of "object type" so that incomplete types are now object types. In C99 and earlier, pointer addition required a pointer to an object type, but `void` was not an object type, so the effect was essentially the same. – Keith Thompson Feb 01 '15 at 03:23
  • @KeithThompson; Sorry for that. I think I copied that from C99 standard. – haccks Feb 01 '15 at 04:47
  • 2
    The last sentence is misleading. It should say "That is why your compiler should have refused the first line of code (adding 1 to the result of `malloc`). The explanation why ignoring this error actually leads to a runtime error in the next statement is another matter. – Marc van Leeuwen Feb 01 '15 at 06:32
0

Saw the other answers, change your code into

int *pointer = (int *)malloc(sizeof(int));

or

int * pointer = new int[1];

will allow you to do what you wanted.

xhg
  • 1,850
  • 2
  • 21
  • 35