Pointers

The address of a variable

On our machine storm.cis.fordham.edu, a memory address (and a pointer that holds a memory address) occupies 64 bits or 8 bytes. That means it could take up to 16 hex digits to write the value of the address.

  1. address.C: output the address of a variable in hexadecimal.
    The value of i is 10
    The address of i is 0x7fff2757276c
    The number of bytes in i is 4
    
  2. Store the address of a variable into a pointer.
    (Diagram with pointer pointing left.)
    Use the data type size_t for a variable holding the number of elements in an array, or the number of bytes in a block of memory.
    1. pointerint.C: store the address of an int into a “pointer to an int”.
    2. pointerdouble.C: store the address of a double into a “pointer to a double”.

Dereference a pointer with the operators   *   [ ]   ->

Dereferencing a pointer means getting the value of the variable that the pointer points to.

  1. The unary * operator dereferences the pointer, i.e., gets the value to which the pointer points.
    (The other * operator is the binary one that performs multiplication.)
    1. dereferenceint.C
    2. dereferencedouble.C
  2. If a pointer points to an element in an array, the pointer also gives us access to the values that are neighbors of the element.
    1. neighborint.C
    2. neighbordouble.C
  3. Handy abreviations. Let’s say we have
    	int a[] {0, 10, 20, 30, 40, 50, 60, 70, 80, 90};
    	int n {size(a)};   //the number of elements in the array
    
    1. In this language, the name of an array all by itself means the address where the array begins. So you can write a in place of &a[0].
    2. In this language, if you add an integer to the address where the array begins, you get the address of the element specified by the integer. Remember, the name of the array all by itself is the address where the array begins. Thus:
      You can write a+1 instead of &a[1].
      You can write a+2 instead of &a[2].
      You can write a+i instead of &a[i].
      You can write a+n instead of &a[n].
    3. Exercise. Make these changes to neighborint.C and neighbordouble.C.
  4. struct.C: use the unary * operator to dereference a “pointer to a structure” to get the value of each field of the original structure.
    See the asterisk in level 3 and the dot in level 2 of the table of operator precedences and associativities.

A picture of an array of ints in memory

	//Assume that the array starts at memory address 1000.
	//The array contains 10 elements, each occupying 4 bytes on our machine.
	//Therefore the array occupies addresses 1000 to 1039 inclusive.

	int a[] {
		 0,   //Occupies addresses 1000 to 1003
		10,   //Occupies addresses 1004 to 1007
		20,   //Occupies addresses 1008 to 1011
		30,   //Occupies addresses 1012 to 1015
		40,   //Occupies addresses 1016 to 1019
		50,   //Occupies addresses 1029 to 1023
		60,   //Occupies addresses 1024 to 1027
		70,   //Occupies addresses 1028 to 1031
		80,   //Occupies addresses 1032 to 1035
		90    //Occupies addresses 1036 to 1039
	};

Note that address 1040 is just beyond the end of the array.

Use a pointer in a loop

  1. loop.C. You can increment (or decrement) a pointer only if it is pointing to an element in an array. The increment (or decrement) will make the pointer point to the next (or to the previous) element in the array.
  2. elementaddress.C. Output the address of each element of an array, in hexadecimal and decimal. When we increment a pointer to an int (with ++p), we are actually adding 4 to the value of the pointer. That’s because each int occupies 4 bytes of memory.
     0   0  0x7ffff4ccdd10  140737300454672     (140 trillion)
     1  10  0x7ffff4ccdd14  140737300454676
     2  20  0x7ffff4ccdd18  140737300454680
     3  30  0x7ffff4ccdd1c  140737300454684
     4  40  0x7ffff4ccdd20  140737300454688
     5  50  0x7ffff4ccdd24  140737300454692
     6  60  0x7ffff4ccdd28  140737300454696
     7  70  0x7ffff4ccdd2c  140737300454700
     8  80  0x7ffff4ccdd30  140737300454704
     9  90  0x7ffff4ccdd34  140737300454708
    
  3. rocket.C: overwrite four consecutive array elements.
  4. movingaverage.C: compute the sum of five consecutive array elements.
    I want to convince you that the expression
    p[-2] + p[-1] + p[0] + p[1] + p[2]
    is simpler than the expression
    a[i-2] + a[i-1] + a[i] + a[i+1] + a[i+2]
  5. Bubblesort, an example from last semester
    1. bubblesort.C: we swap the two adjacent array elements a[j] and a[j+1].
    2. bubblesortintptr.C: we swap the two adjacent array elements q[0] and q[1].

Use a pointer as an argument to a function.

  1. pass.C: three ways to pass an int to a function.
    1. The function f receives a copy of the variable i, and can change the value of the copy. (The name of the copy is a.) But changing the value of the copy has no effect on the value of i.
    2. b is just another name for the variable j. This allows the function to change the value of j. No copy of j is created.
    3. The function receives the address of the variable k, and can use this address (dereferenced with a *) to install a new value into k.
    The statement that calls the function (j(i, j, &k);) makes it obvious that the function can change the value of k. But it is dangerously unobvious that the function can also change the value of j.
  2. array.C. Pass the address of the start and end (actually, just beyond the end) of an array to a function.
  3. passstruct.C. Pass the address of a structure to a function. We had to tell the computer what a month is before we could tell it what f is. Believe it or not, passing the address of a structure to a function is the jumping-off point into the world of Object-Oriented Programming!
  4. llawrence18
    1. structure.C
    2. structure1.C with “bitwise and” (&) and “bitwise or” (|).
        00000000000000000000000000000010   Monday
        00000000000000000000000000000100   Tuesday
        00000000000000000000000000001000   Wednesday
        00000000000000000000000000010000   Thursday
      | 00000000000000000000000001000000   Saturday
        00000000000000000000000001011110
      
    3. structure2.C holds the three structures in an array of structures.
  5. jcrews1.C: sort an array of structures.
  6. jr224.C: search an array instead of executing a series of if statements.

Two ways to make a pointer const

You can insert the keyword const into a declaration in two places:

  1. at the start of the declaration
  2. immediately after an asterisk in the declaratation
	int i {10};
	int j {20};
	int *p {&i};   //p points to i.

The following diagram shows two regions of memory, the pointer on the right and the pointed-to variable on the left.

  1. const.C. Not allowed to change the value of a const variable. It always holds the same value.
    If you uncomment the ++j in line 18, the c++ compiler will say
    const.C:18:11: error: increment of read-only variable ā€˜jā€™
    
  2. constptr.C. Not allowed to change the value of a const pointer. It always points to the same place.
  3. readonly.C. A read-ony pointer is a needle that can’t scratch the record.
  4. both.C. A pointer that is const in both ways.