Rylik

Pointer Arithmetic

When doing pointer arithmetic with a type, the increment in bytes is always the size of that type. For example

struct point{
    float  x;
    float  y;
};
#define MAX_POINTS 1024
struct point *points;

int main(void)
{
    points = alloc(sizeof(*points) * MAX_POINTS);

    struct point *start = points;
    struct point *end = points + MAX_POINTS;
    while (start != end) {
        struct point current_point = *start;
        start++;
    }
}

A couple of things are going on there with the math:

  • because we are allocating bytes, we need to do that multiplication. The element count is 1024 so we need 1024 times the size of our struct, which we can get with sizeof and dereferencing the pointer.
  • we set start and end pointer, but for end, we only do + 1024. Doing the multiplication would be wrong, because points already increments by its type size ( which is 8 bytes, 4 bytes per float). That means that points + 1 advances 8 bytes ( one element ).

To clear something up with dereferencing, I will show exactly, step-by-step, what the indexing counterpart is:

struct point{
    float  x;
    float  y;
};
#define MAX_POINTS 1024
struct point *points;

int main(void)
{
    points = alloc(sizeof(*points) * MAX_POINTS);

    /*Right now, *points would be equivalent to points[0]*/
    struct point *base = points;
    struct point v1 = *points;
    struct point v2 = points[0];
    /*So here, v1 == v2*/
    points++;
    v1 = *points;
    v2 = base[1];
    /*
    *Again, v1 == v2
    *Same thing will happen if we continue.
    *Note: Incrementing the variable points does not also increment base.
    */
    points++;
    v1 = *points;
    v2 = base[2];
    /* Still v1 == v2 */
}

Ok, so what if we’re doing pointer arithmetic with an “untyped type”, like:

   int *a = alloc(1024 * sizeof(*a)));
   void *ptr = a;
   ptr++;

In the case of a void star, the increment will be 1 byte when using GCC / Clang, but otherwise, this is undefined behaviour.

Now that we got to this “untyped type”, let’s talk about casting. In C, you can cast anything into a void star and out of a void star. Generally speaking, you can cast pretty freely regardless. This means:

struct point{
    float  x;
    float  y;
};


/*just for example purposes */
struct newtype{
   int  a,b,c;
   struct point  position;
   struct point  velocity;
};
#define MAX_POINTS 1024
struct point *points;

int main(void)
{
    points = alloc(sizeof(*points) * MAX_POINTS);
    void *ptr = points;

    /*Now we cast the void* into a float* and then dereference it in order to get the value out*/
    float v1 = *(float*)ptr;

    /*
    *Here we reinterpret the void* into  newtype*
    */
    struct newtype *newptr = (struct newtype *)ptr;

    /*
    *This is legal.
    *An extreme example, but just to demonstrate that anything can be reinterpreted as any other type through casting,
    *regardless of it being a void * or not
    */
    (*(struct point*)(newptr)).x = 420.69f;
    (*(char*)(newptr)) = ‘a’;
}

Note:

  • Pointer arithmetic is done in this way on the CPU level. This isn't specific to C.
  • If a language does not allow you to do pointer arithmetic, then the language does it on your behalf.

Found this useful? Support the writing on Ko-fi.