0

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 *));
gcr
  • 443
  • 2
  • 5
  • 14
  • 1
    You cast to `char*` if you want to do pointer arithmetic on byte addresses. – Barmar Jan 03 '21 at 06:05
  • 4
    It's done so that `*(ptr+x) == ptr[x]` – Barmar Jan 03 '21 at 06:06
  • Not accounting for the size of the pointee is asking for alignment errors. Why *wouldn't* you want to account for the pointee size when using a pointer type that isn't `char*`? – jamesdlin Jan 03 '21 at 06:10
  • you need to move by `index + sizeof(element)` not by `index + 1` – Loveen Dyall Jan 03 '21 at 06:18
  • See also [this question](https://stackoverflow.com/q/916051/5684257). On some architectures, pointers *aren't* just the addresses of bytes in memory, and you *can't* have e.g. an `int*` that points "halfway" inside an `int`, because `int*` (on that architecture) is basically "divided by" `sizeof(int)` already. So we *have* to abstract the size of the pointees away, and if you really want to manipulate addresses in bytes, you have to cast them (which, on these architectures, wouldn't be a no-op like it is on modern mainstream architectures). – HTNW Jan 03 '21 at 06:24

2 Answers2

1
  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.
hobbs
  • 223,387
  • 19
  • 210
  • 288
  • 2
    [Pointer arithmetic on `void *` is illegal.](https://stackoverflow.com/questions/3523145/pointer-arithmetic-for-void-pointer-in-c) It's only allowed by GCC as a compiler extension. – John Kugelman Jan 03 '21 at 06:30
  • Use `char*` instead. `sizeof(char)` is guaranteed to be 1. But the resulting pointer may not be suitably aligned for the type you are casting it to. – koder Jan 03 '21 at 08:37
0

Instead of such monsters use some temporary variables to make code a bit more readable.

Pointer arithmetic on the byte level:

char *dest = (char *)arr->elements + sizeof(char *) * (index + 1);
char *src =  (char *)arr->elements + sizeof(char *) * index;

memmove(dest, src, (arr->count - index) * sizeof(char *));
0___________
  • 60,014
  • 4
  • 34
  • 74