What is a pointer?
Consider an example with char pointers:
(source file)
Examining pointers in a debugger:
Let's start with an example:
int main ()
{
int i = 5;
int *intPtr;
// Extract the address of variable i into the pointer:
intPtr = & i;
// Print this address:
printf ("Variable i is located at address %p\n", intPtr);
// Use the address:
*intPtr = *intPtr + 1;
// See what happens:
printf ("i = %d\n", i); // What is in i now?
}
Note:
intPtr = & i;
printf ("Variable i is located at address %p\n", intPtr);
*intPtr = *intPtr + 1;
Exercise 3.1:
Type up, compile and execute the above program. Add a second
pointer variable that points to
i
and modifies
i
using only the second pointer variable. Can two pointers point
to the same thing?
A more complex example:
(source file)
// Declare some global integers:
int i = 5;
int j = 6;
int sum;
int main ()
{
// Int pointer declarations:
int *intPtr;
int *intPtr2;
int *intPtr3;
// First, let's print the address of variable i:
printf ("Variable i is located at address %p\n", &i);
// Now extract the address of variable i into the pointer:
intPtr = & i;
// Print.
printf ("The int at memory location %p is %d\n", intPtr, *intPtr);
// Let's do some arithmetic.
intPtr2 = & j;
sum = (*intPtr) + (*intPtr2);
printf ("The sum of %d and %d is %d\n", *intPtr, *intPtr2, sum);
// Now for something stranger:
printf ("The integer at location %p is %d\n", (intPtr+1), *(intPtr+1));
printf ("The integer j=%d is at location %p\n", j, &j);
// Let's see what the default initial value of intPtr3 is:
if (intPtr3 == NULL) {
printf ("intPtr3 has been initialized to NULL\n");
}
else {
printf ("intPtr3 has been initialized to %p\n", intPtr3);
}
}
Note:
// Now extract the address of variable i into the pointer:
intPtr = & i;
sum = (*intPtr) + (*intPtr2);
printf ("The integer at location %lu is %d\n", (intPtr+1), *(intPtr+1));
In this case, we examine the next address beyond i, which
happens to be where j is stored.
int main ()
{
int i = 5;
char *charPtr; // Pointer declaration
int j;
// Make the char pointer point to the start of the integer:
charPtr = (char*) (& i);
// Extract the byte, store it in the integer j and print j.
j = (int) *charPtr;
printf ("First byte: %d\n", j);
// Get the next byte and print:
j = (int) *(charPtr+1);
printf ("Second byte: %d\n", j);
// Get the third byte and print:
j = (int) *(charPtr+2);
printf ("Third byte: %d\n", j);
// Get the fourth byte and print:
j = (int) *(charPtr+3);
printf ("Fourth byte: %d\n", j);
}
Exercise 3.2:
In addition to the value in
j
also print the address in
charPtr
in the first
printf
statement.
Note:
char *charPtr; // Pointer declaration
j = *charPtr; // Retrieve whatever charPtr points to.
gcc -g -o pointer2 pointer2.c
and run inside the debugger:
gdb pointer2
(gdb) l
7
8 int main ()
9 {
10 // Int pointer declarations:
11 int *intPtr;
12 int *intPtr2;
13 int *intPtr3;
14
15 // First, let's print the address of variable i:
16 printf ("Variable i is located at address %p\n", &i);
(gdb) break 16
Breakpoint 1 at 0x10720: file pointer.c, line 16.
(gdb) run
Starting program: pointer2
Breakpoint 1, main () at pointer2.c:16
16 printf ("Variable i is located at address %p\n", &i);
(gdb) p intPtr
$1 = (int *) 0x0
(gdb) next
Variable i is located at address 134000
19 intPtr = & i;
(gdb) next
22 printf ("The int at memory location %p is %d\n", intPtr, *intPtr);
(gdb) p intPtr
$2 = (int *) 0x20b70
(gdb) p *intPtr
$3 = 5
(gdb)
The l (list) command lists the program.
The break command creates a breakpoint so you can
stop midway through execution.
The p (print) command prints variables.
The next command executes the next statement.
Exercise 3.3:
Write a program in
pointertopointer.c
to use a pointer to a pointer to an int.
Fill in the needed assignments below to make the program
print "5".
int main ()
{
int i = 0;
int *p = NULL;
int **p2 = NULL;
// Fill in the assignments here to make the program work:
// Should print "5";
printf ("i = %d\n", **p2);
}
Strings in C:
Example:
(source file)
int main ()
{
char *hStr = "hello"; // String initialization.
char *wStr = "world";
char ch;
// Note the use of "%s"
printf ("%s %s\n", hStr, wStr);
printf ("3rd char in wStr is %c\n", *(wStr+2)); // Prints 'r'
// Let's get the 6-th char: should be '\0'
ch = *(wStr+5);
if (ch == '\0') {
printf ("char is string terminator\n");
}
else {
printf ("char is not string terminator\n");
}
}
Note:
There are two ways of creating an array in C:
Let's look at an example:
(source file)
int main ()
{
int A[10]; // Statically-sized unidimensional array.
double B[20][20]; // 2D array.
int *A2 = NULL; // Declaration of 1D array variable.
double **B2 = NULL; // Declaration of 2D array variable.
int i, j; // For-loop variables.
int sum; // For use in examples.
double dSum;
// Static example. The space is already allocated, so the
// array can be used immediately.
for (i=0; i < 10; i++) {
A[i] = i * 100; // Fill the array with some numbers.
}
sum = 0;
for (i=0; i < 10; i++) {
sum += A[i]; // Compute the sum of array's numbers.
}
printf ("sum = %d\n", sum); // Print sum.
// Dynamic version of example using "malloc" to allocate space.
// Note the use of the "sizeof" keyword.
A2 = (int*) malloc (sizeof(int) * 10);
for (i=0; i < 10; i++) {
A2[i] = i * 100; // Fill the array with some numbers.
}
sum = 0;
for (i=0; i < 10; i++) {
sum += A2[i]; // Compute the sum of array's numbers.
}
printf ("sum = %d\n", sum); // Print sum.
// 2D example with static array.
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
B[i][j] = i*j; // Fill the array with some numbers.
}
}
dSum = 0;
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
dSum += B[i][j]; // Compute the sum of array's numbers.
}
}
printf ("dSum = %lf\n", dSum); // Print sum.
// Dynamic version of 2D example. Note the two-step allocation.
// Allocate space for 20 pointers-to-double
B2 = (double **) malloc (sizeof(double*) * 20);
for (i=0; i < 20; i++) {
// For each such pointer, allocate a size-20 double array.
B2[i] = (double*) malloc (sizeof(double) * 20);
}
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
B2[i][j] = i*j; // Fill the array with some numbers.
}
}
dSum = 0;
for (i=0; i < 20; i++) {
for (j=0; j < 20; j++) {
dSum += B2[i][j]; // Compute the sum of array's numbers.
}
}
printf ("dSum = %lf\n", dSum); // Print sum.
// Must free allocated memory when not needed anymore:
free (A2);
free (B2);
}
Note:
by
using the sizeof operator.
A2 = (int*) malloc (sizeof(int) * 10);
Note:
int *A2 = NULL; // Declaration of 1D array variable.
spaceNeededInBytes = sizeof(int) * 10;
A2 = (int*) malloc (spaceNeededInBytes);
A2 = (int*) malloc (sizeof(int) * 10);
B2 = (double **) malloc (sizeof(double*) * 20);
for (i=0; i < 20; i++) {
B2[i] = (double*) malloc (sizeof(double) * 20);
}
Note:
B2 = (double **) malloc (sizeof(double*) * 20);
for (i=0; i < 20; i++) {
B2[i] = (double*) malloc (sizeof(double) * 20);
}
Let's start with a simple example:
(source file)
Another example:
(source file)
#include <stdio.h>
#include <stdlib.h>
int main ()
{
// Declare a pointer to an int.
int *p;
// Get the space from malloc:
p = (int*) malloc (sizeof(int));
// Assign a value and print:
*p = 5;
printf ("Int value: %d\n", *p);
// Free the memory when done:
free (p);
}
Note:
// Declare a pointer to an int.
int *p;
simply declares the pointer variable. The pointer may itself is
invalid since it hasn't been assigned a value yet.
int main ()
{
// Declare a pointer to an int.
int *p;
printf ("Initial value of pointer: %p\n", p); // Prints 0 (C99)
}
Thus, the "picture" after declaration is:

int main ()
{
int *p;
int i;
// Extract the address of i and put that in p:
p = &i;
}
int main ()
{
int *p;
// Get the space from malloc:
p = (int*) malloc (sizeof(int));
}

*p = 5;
printf ("Int value: %d\n", *p);
After this assignment, the picture is:

int main ()
{
int *p;
p = (int*) malloc (4);
*p = 5;
printf ("Int value: %d\n", *p);
}
This works if we happen to know that an int takes up
4 bytes.
int main ()
{
int *p;
p = (int*) malloc (16);
*p = 5;
printf ("Int value: %d\n", *p);
}
Here, 16 contiguous bytes are allocated, of which only 4 are used.
printf ("The number of bytes needed by an integer: %d\n", sizeof(int));
printf ("The number of bytes needed by a double: %d\n", sizeof(double));
p = (int*) malloc (sizeof(int));
An easy way to remember this is: "the cast type is the same as
the argument
to sizeof but with a *
appended".
#include <stdio.h>
#include <stdlib.h>
int main ()
{
// Declare a pointer to an int.
int *p;
// Print the initial value of the pointer:
printf ("Initial value of pointer: %p\n", p); // Prints 0 (C99)
// Get the space from malloc:
p = (int*) malloc (sizeof(int));
// Print the address of the memory block returned by malloc:
printf ("Current pointer: %p\n", p);
}
// Free the memory when done:
free (p);
#include <stdio.h>
#include <stdlib.h>
int main ()
{
double *doublePtr;
char *charPtr;
printf ("The number of bytes needed by a double: %d\n", sizeof(double));
printf ("The number of bytes needed by a char: %d\n", sizeof(char));
// Get the space from malloc:
doublePtr = (double*) malloc (sizeof(double));
charPtr = (char*) malloc (sizeof(char));
// Print the address of the memory block returned by malloc:
printf ("double pointer is at location: %p\n", doublePtr);
printf ("char pointer is at location: %p\n", charPtr);
// Assign values and print:
*doublePtr = 3.141;
*charPtr = 'A';
printf ("double value = %lf char value = %c\n", *doublePtr, *charPtr);
// Free the memory when done:
free (doublePtr);
free (charPtr);
}
Note:
doublePtr = (double*) malloc (sizeof(double));
charPtr = (char*) malloc (sizeof(char));
An example using arrays:
(source file)
#include <stdio.h>
#include <stdlib.h>
int main ()
{
int A[10];
int *B;
printf ("BEFORE malloc: Address A=%p Address B=%p\n", A, B);
// Prints an address for A, but zero for B
// Assign space to B:
B = (int*) malloc (sizeof(int) * 10);
printf ("AFTER malloc: Address A=%p Address B=%p\n", A, B);
// Prints an address for A and newly allocated address for B.
// Two ways of assigning values:
A[3] = 5;
*(A + 4) = 6;
printf ("A[3]=%d A[4]=%d\n", A[3], A[4]); // Prints 5 and 6.
// Two ways of assigning values:
B[3] = 7;
*(B + 4) = 8;
printf ("B[3]=%d B[4]=%d\n", B[3], B[4]); // Prints 7 and 8.
// Done.
free (B);
}
Note:

A[3] = 5;
*(A + 4) = 6; // Fifth position in array A.
*(A + 4) = 6;
assigns the value 6 into this location.
Next, let's consider 2D arrays:
(source file)
#include <stdio.h>
#include <stdlib.h>
int main ()
{
double A[10][10]; // Static allocation of space to array A.
double **B; // We'll use malloc to assign space to B.
int i;
printf ("BEFORE malloc: Address A=%p Address B=%p\n", A, B);
// Prints an address for A, but zero for B
// Assign space for first dimension of B (an array of pointers-to-double)
B = (double**) malloc (10 * sizeof(double*));
for (i=0; i<10; i++) {
B[i] = (double*) malloc (10 * sizeof(double));
}
printf ("AFTER malloc: Address A=%p Address B=%p\n", A, B);
// Prints an address for A and newly allocated address for B.
// Two ways of assigning values:
A[3][4] = 5.55;
*((double*)A + 5*10 + 6) = 6.66;
printf ("A[3][4]=%lf A[5][6]=%lf\n", A[3][4], A[5][6]); // Prints 5.55 and 6.66.
// Twy ways of assigning values:
B[3][4] = 7.77;
*((*(B + 5)) + 6) = 8.88;
printf ("B[3][4]=%lf B[5][6]=%lf\n", B[3][4], B[5][6]); // Prints 7.77 and 8.88
// Free the individual small arrays:
for (i=0; i<10; i++) {
free (B[i]);
}
// Free the array of double-pointers:
free (B);
}
Note:


A[3][4] = 5.55;
B[3][4] = 7.77;
*((double*)A + 5*10 + 6) = 6.66;
*((*(B + 5)) + 6) = 8.88;
*((double*)A + 5*10 + 6) = 6.66;
*((*(B + 5)) + 6) = 8.88;
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1