cpointer-arithmeticmemmove

Why does C abstract away sizeof during pointer arithmetic?


I was recreating an array from scratch in C, with implementation of common functions such as append, insert, etc. I got most of the way, but when I tried running my "insert" function at index 1 (as opposed to 0), I started getting segfault errors during "for loop" output printing accessing the last element. I only used loops for the printing, otherwise for resizing and shifting I used memory management functions like memmove and strdup and realloc. I also noticed it overwrote index 1 instead of sliding the array like I expected from memmove.

I figured out I was doing pointer arithmetic wrong, using the sizeof operator each time, but it got me thinking, why does C abstract this away from us outside of malloc()? If we want to do arithmetic by less than a whole unit, how would we do that? Would we copy the value to another variable and do it there, and would there be any such use cases?

Funny thing is I've been doing it this way for a few months with no problems, and in fact I could have sworn it was necessary once, but maybe I'm not thinking straight. Maybe that was malloc or realloc or something else or maybe I just got lucky.

Here's some of the code I was talking about (runs well):

void arr_insert(Array *arr, char *element, int index)
{
  // Throw an error if the index is greater than the current count
  if (index > arr->count)
  {
    fprintf(stderr, "Index out of range");
    printf("Index out of range in arr_insert\n");
    return;
  }
  // Resize the array if the number of elements is over capacity
  if (arr->count == arr->capacity) resize_array(arr);
  // Move every element after the insert index to the right one position
  memmove(arr->elements + ((index + 1)), arr->elements + ( index), (arr->count - index) * sizeof(char *));
  // Copy the element (hint: use `strdup()`) and add it to the array
  *(arr->elements + (index)) = strdup(element);
  // Increment count by 1
  arr->count++;

This is the line that I had before which failed

 memmove(arr->elements + (sizeof(char *) * (index + 1)), arr->elements + (sizeof(char *) * index), (arr->count - index) * sizeof(char *));

Solution

    1. Because it makes array indexing work (remember, a[i] is just *(a+i).)
    2. Because it makes sure that the result of pointer arithmetic is always a pointer that is aligned properly for the type.
    3. You can cast a pointer to void * if you want to operate on it directly as a series of bytes (this is how functions like memcpy and write work), and you can cast it back (malloc, realloc). But doing something like (int *)((void *)x + 1) (where sizeof(int) != 1) is not guaranteed to do anything useful or even to behave consistently.