int x=3; // happy bunnyYou can also do pointer arithmetic, where you change what the pointer points to. Pointer arithmetic is dangerous, because you can easily meddle with forces you can barely understand, much less control:
int *p=&x; // point to the happy bunny
std::cout<<"The pointer is "<<p<<endl;
int i=*p; // summon the happy bunny
return i;
int x=3; // happy bunny
int *p=&x; // point to the happy bunny
for (int r=0;r<1000000;r++) p++; // move way past the bunnies
std::cout<<"The new pointer is "<<p<<endl;
int i=*p; // summon the EVIL DEMON MONSTER
return i;
As you might expect, even reading from a way-bad pointer can cause
your program to die horribly. So you have to make sure that what
you're pointing to is really there. This is doubly true for
writing--you have to make sure it's there, and
that you're allowed to write to it. The standard way to do this
is just to be very careful about how you write your pointer
manipulation code.
Here's some valid pointer manipulation code, where we use ++ to move
the pointer to the next integer, -- to move it back to the previous
integer, and then we add two to jump over two integers at once:
int arr[4];
arr[0]=100;
arr[1]=101;
arr[2]=102;
arr[3]=103;
int *p=arr; /* points to arr[0] */
std::cout<<"At p: "<< *p <<endl;
p++; /* move pointer down, to arr[1] */
std::cout<<"After p++: "<< *p <<endl;
p++; /* move pointer down some more, to arr[2] */
std::cout<<"After another p++: "<< *p <<endl;
p--; /* move pointer back, to arr[1] again */
std::cout<<"And then a p--: "<< *p <<endl;
p=p+2; /* moves by 2 *ints*, to arr[3] */
std::cout<<"p=p+2: "<< *p <<endl;
return 0;
Note that this means that arrays are just a series of items at increasing addresses in memory. That's all an array is.
In C/C++, the compiler knows you're pointing to an integer.
So when you say "p=p+2", the compiler moves the pointer by two
integers, which is a total of eight bytes. You can see that byte
count by printing out the pointers as they move, like the
following. (Note now we're printing "p" the pointer; not "*p" the
integer.)
int arr[4];
arr[0]=100;
arr[1]=101;
arr[2]=102;
arr[3]=103;
int *p=arr; /* points to arr[0] */
std::cout<<"At p: "<< p <<endl;
p++; /* move pointer down, to arr[1] */
std::cout<<"After p++: "<< p <<endl;
p++; /* move pointer down some more, to arr[2] */
std::cout<<"After another p++: "<< p <<endl;
p--; /* move pointer back, to arr[1] again */
std::cout<<"And then a p--: "<< p <<endl;
p=p+2; /* moves by 2 *ints*, to arr[3] */
std::cout<<"p=p+2: "<< p <<endl;
return 0;
Pointers literally *are* just this byte count. You can do
pointer arithmetic on byte counts in C++ by typecasting your pointers
to "char *", but the syntax looks a little weird, because to access an
int, you have to cast back to "int *":
int arr[4];
arr[0]=100;
arr[1]=101;
arr[2]=102;
arr[3]=103;
char *p=(char *)arr; /* points to the bytes in arr[0] */
std::cout<<"At p: "<< *(int *)p <<endl;
p+=4; /* move pointer down, to arr[1] */
std::cout<<"After p++: "<< *(int *)p <<endl;
p+=4; /* move pointer down some more, to arr[2] */
std::cout<<"After another p++: "<< *(int *)p <<endl;
p-=4; /* move pointer back, to arr[1] again */
std::cout<<"And then a p--: "<< *(int *)p <<endl;
p+=8; /* moves by 2 *ints*, to arr[3] */
std::cout<<"p=p+2: "<< *(int *)p <<endl;
return 0;
Byte pointers are useful to learn in C++, because they're all you get in assembly language!
mov eax, DWORD [myIntPtr] ; read memory here (like C++: return *myIntPtr;)You can copy a pointer value into a register, too. Here we're dereferencing a pointer stored in a register:
ret
myIntPtr: ; A place in memory, where we're storing an integer.
dd 123 ; "data DWORD", our integer
mov edx, someIntPtr ; copy the address myIntPtr into edx (like C++: p=someIntPtr;)A pointer to an array initially looks just like a pointer to anything else:
mov eax, DWORD [edx] ; read memory edx points to (like C++: return *p;)
ret
someIntPtr: ; A place in memory, where we're storing an integer.
dd 123 ; "data DWORD", our integer
mov ecx, myArray ; ecx points to myArray (like C++: p=arr;)
mov eax, DWORD [ecx] ; read memory pointed to by ecx (like C++: return *p;)
ret
myArray: ; A place in memory, where we're storing some integers.
dd 100 ; "data DWORD", here our array element [0]
dd 101 ; [1]
dd 102 ; [2]
dd 103 ; [3]
But since we're pointing to an array, we can move the pointer up to
point to the next element of the array, and then dereference
that. Careful! In assembly, you have to adjust pointers by
the number of *bytes* they point to, not integers!
mov ecx,myArray ; ecx points to myArray
add ecx,4 ; point to the next integer (four bytes down)
mov eax, DWORD [ecx] ; read memory pointed to by ecx
ret
myArray: ; A place in memory, where we're storing some integers.
dd 100 ; "data DWORD", here our array element [0]
dd 101 ; [1]
dd 102 ; [2]
dd 103 ; [3]
There's even a special optional assembly syntax for accessing
arrays, where you compute the memory address by starting at one
register (the start of the array), and adding in a scaled version of
another register (the array index):
mov ecx,myArray ; ecx points to myArray
mov edx,2 ; we want integer index [2]
mov eax, DWORD [ecx + 4*edx] ; read memory from array ecx, at int index edx
ret
myArray: ; A place in memory, where we're storing some integers.
dd 100 ; "data DWORD", here our array element [0]
dd 101 ; [1]
dd 102 ; [2]
dd 103 ; [3]
One limitation of the little static arrays written above is that we
can't write to these arrays. For that, we need read-write
temporary storage space, and we get that from the stack.
sub esp,4 ; I claim the next four bytes in the name of... me!Here's how we'd allocate one hundred integers on the stack, then use just one of them:
mov DWORD [esp],1492 ; store an integer into our stack space
mov eax,DWORD [esp] ; read our integer from where we stored it
add esp,4 ; Hand back the stack space
ret
sub esp,400 ; I claim the next four hundred bytesThere are even special instructions for putting stuff onto the stack and taking it back off, called "push" and "pop":
mov edi,esp ; points to the start of our 100-integer array
add edi,160 ; jump down to integer 40 in the array
mov DWORD [edi],1492 ; store an integer into our stack space
mov eax,DWORD [edi] ; read our integer from where we stored it
add esp,400 ; Hand back the stack space
ret
push 19This prints the "19" that's stored on top of the stack. In general, all your function arguments are stored on the stack. This means the stack is a rather funny mix of function arguments, local and temporary variables, and "other stuff" (to be discussed on Friday!).
extern print_int
call print_int
pop eax ; MUST clean up the stack
ret