The compiler has type information available to know how to generate the underlying machine code necessary to evaluate something like ptr + 1
. At the machine level it's the equivalent of saying ptr + sizeof(*ptr)
. So, if you've got code like this:
int *ptr = // some address;
ptr++;
Then the compiler (on x86) will emit something a bit like this for the ptr++
:
mov eax,dword ptr [ptr] ; Load the pointer
add eax,4 ; Add 4 to the address as sizeof(*ptr) is 4
mov dword ptr [ptr],eax ; Write the new value back
The fact that the compiler has the type information means that the evaluation of ptr + 1
is static, rather than dynamic and can cause you problems when you've got arrays of derived types that you pass by pointer to base types. Take this example:
struct Base
{
int x;
};
struct Derived : Base
{
int y;
};
void PrintAll(Base *p, int numberOfItems)
{
while(numberOfItems--)
{
std::cout << p->x << std::endl;
p++; // Oops!!!
}
}
Derived data[10];
Derived *ptr = data;
PrintAll(ptr, 10);
The line marked "Oops!!!" doesn't work as expected. Although ptr
actually points to objects of type Derived
it will only actually try to move ptr
to point to the next Base
rather than Derived
as that all the compile time (ie static) information the compiler has available to it. This causes undefined behaviour. If ptr
had actually pointed to an array of Base
then we'd have been ok. Bugs like this are hard to detect at compile time and this is why you're better of using a container such as std::vector
for this sort of thing.