I am stumped when it comes to what the actual effect of the malloc is
The malloc call allocates the space for your array.  When you initially declare ptr, it's not initialized to point to a valid memory location:
     +---+
ptr: |   | ----> ???
     +---+
Attempting to read or write through ptr at this time will lead to undefined behavior; your code may crash outright, or it may corrupt storage somehow, or it may appear to run without any issues.
The malloc call allocates space from the heap (a.k.a., a dynamic memory pool) and assigns the address of the first element of that space to ptr:
     +---+
ptr: |   | ---+
     +---+    |
      ...     |
       +------+
       |
       V
     +---+
     |   | ptr[0]
     +---+
     |   | ptr[1]
     +---+
      ...
Please note that the (int *) cast on the malloc call has not been necessary since the 1989 standard, and is actually considered bad practice (under C89, it could mask a bug).  IMO, the best way to write a malloc call is
T *p = malloc( N * sizeof *p );
where T is any type, and N is the number of elements of type T you want to allocate.  Since the expression *p has type T, sizeof *p is equivalent to sizeof (T).  
and what all of the *(ptr + i) actually does.
*(ptr + i) is equivalent to ptr[i], so
*ptr = 'x';
*(ptr + 1) = 'x';
are equivalent to writing
ptr[0] = 'x';
ptr[1] = 'x';
Please note that
*(ptr +99) = 'x';
is outside the range of the array you've allocated; you only set aside enough space for 25 integers.  Again, this operation (and any operation *(ptr + i) = 'x'; where i is greater than 24) will lead to undefined behavior, and your code may crash, corrupt data, or otherwise.  
Pointer arithmetic takes the pointed-to type into account; ptr + 1 yields the address of the next integer object following the one at ptr.  Thus, if ptr is 0x8000 and sizeof (int) is 4, then ptr + 1 yields 0x8004, not 0x8001.