It doesn't matter how fast your code is if it gets tossed because nobody else can understand and maintain it; also, tricky code can sometimes prevent your compiler from making a better optimization.
For example:
int main(void)
{
int arr[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int *p = *arr; // == arr[0] == &arr[0][0]
int x;
x = arr[2][3]; // Straightforward array access
x = *(*(arr+2)+3); // Ugly pointer arithmetic
x = *(ptr + 11); // Slightly less ugly pointer arithmetic
return 0;
}
I ran the above through gcc -c -g -Wa,-a,-ad > foo.lst to get the generated assembly and source code interleaved.
Here's the translation for x = arr[2][3];:
movl -16(%ebp), %eax
movl %eax, -8(%ebp)
Here's the translation for x = *(*(arr+2)+3);:
leal -60(%ebp), %eax
addl $44, %eax
movl (%eax), %eax
movl %eax, -8(%ebp)
And finally, the translation for x = *(ptr + 11);:
movl -12(%ebp), %eax
addl $44, %eax
movl (%eax), %eax
movl %eax, -8(%ebp)
Don't try to outsmart your compiler. This isn't the 1970s anymore. gcc knows how to do array accesses efficiently without you having to tell it.
You shouldn't even be thinking about performance at this level unless you've tuned your algorithm and data structures, used the highest optimization settings on your compiler (FWIW, -O1 generates the same code for all three versions), and you're still failing to meet a hard performance requirement (in which case, the right answer is usually to buy faster hardware). And you shouldn't change anything without first running your code through a profiler to find the real bottlenecks. Measure, don't guess.
EDIT
Naturally, when the literals 2 and 3 are replaced by variables, the situation changes. In that case, the *(ptr + offset); comes out looking the best. But not by much. And I'll still argue that clarity matters more at this level.