Well, not only because of that, it's also just dereferencing a single pointer, and since the size of the array is known at compile-time, the compiler can just generate code that produces the correct offset. To get something like that dynamically, you could malloc a single flat chunk of memory large enough to hold the entire 2D array and then calculate the 2D offsets manually. Or you could define an array that holds pointers to the rows, and instead of another malloc for each row, point them at row offsets in that flat array.