LABORATORY 08

Pointer Variables. Reference Variables.

Duration: 2 hours Language: C / C++ Previous: Laboratory 7 PDF handout RO versiunea română

Pointers are variables that hold memory addresses as their value. They are the mechanism that makes dynamic allocation, linked data structures, and modifying a function's parameters possible. They are also the source of the most frequent serious bugs in a C program.

1Lab objectives

xvalue: 42address: 0x1000pvalue: 0x1000address: 0x2000p holds the address of xp = &x | *p is x*p = 7 modifies x, not p
Fig. - A pointer is an ordinary variable, with its own address, whose value is the address of another variable. The & operator gets the address, the * operator reaches the value at that address.
  • Declaring pointers and understanding the role of the * symbol
  • Using the & (address) and * (dereference) operators
  • Applying the rules of pointer arithmetic
  • Exploiting the link between pointers and arrays
  • Modifying a function's parameters through pointers and through references

2What a pointer is

Every variable occupies a memory area that has an address. A pointer is an ordinary variable, with the difference that the value it holds is precisely such an address.

CategoryContentsExample
Data pointersaddresses of variables or constantsint *p = &x;
Function pointersaddresses of executable codeint (*f)(int);
An analogyIf a variable is a house, its value is what is inside, and its address is the number on the street. A pointer is a slip of paper on which you wrote that number: it does not contain the house, only the indication of where to find it.

3Declaration and operators

syntax
type * variable_name;

int    *p_i;        // pointer to int
float  *p_f;        // pointer to float
int    *tab_p[10];  // array of 10 pointers to integers
float **p_p;        // pointer to pointer
void   *p_v;        // generic pointer: can hold the address of any type
OperatorNameWhat it doesExample
&reference-ofreturns the address of a variablep = &x;
* (in a declaration)type markershows that the variable is a pointerint *p;
* (in expressions)dereferenceaccesses the value at the stored addressy = *p;
The same symbol, two meaningsIn int *p; the star is part of the type. In *p = 7; the star is an operator meaning "write to the address held by p". The distinction is made by context: declaration or expression.
The void pointervoid * can hold the address of any data type, but cannot be dereferenced directly - the compiler does not know how many bytes to read. An explicit conversion is required: *(int *)p_v.

4Memory simulator

Change the pointer's target and watch the difference between p, *p, and &p. Select NULL to see what a pointer that points to nothing means.

A pointer and the variables it can point to

5Pointer arithmetic

Adding an integer to a pointer does not add bytes, it adds elements. The compiler automatically multiplies by the size of the pointed-to type.

OperationActual effectFor int * (4 bytes)
p + 1advances by 1 elementthe address increases by 4
p + nadvances by n elementsthe address increases by 4·n
p - qthe number of elements between them(the address difference) / 4
p++moves to the next elementthe address increases by 4
ConsequenceThe same expression p + 1 advances by 1 byte for char *, by 4 for int *, and by 8 for double *. The pointer's type is essential, not decorative.

6Pointers and arrays

An array's name is, in most contexts, precisely the address of the first element. That is why indexing and pointer arithmetic are equivalent notations:

Index notationPointer notationMeaning
v&v[0]the address of the first element
v[i]*(v + i)the element at position i
&v[i]v + ithe address of element i
p[i]*(p + i)works for pointers too
The essential differenceAn array is not a pointer. sizeof(v) for int v[10] gives 40, while sizeof(p) for int *p gives 4 or 8 - the size of an address. Also, the array's name cannot be modified: v++ is an error, p++ is allowed.

7Passing parameters to functions

In C, parameters are passed by value: the function receives a copy. Changes made inside are lost on return. To modify the original variable, its address must be passed.

ModeSyntaxCan modify the original?Available in
By valuevoid f(int x)noC and C++
By pointervoid f(int *x)yes, with *x = ...C and C++
By referencevoid f(int &x)yes, directly with x = ...C++ only

8Reference variables (C++ only)

A reference is an alternative name for an existing variable. Unlike a pointer, it occupies no memory of its own and has no special syntax when used.

reference.cpp
int x = 10;
int &r = x;        // r becomes another name for x

r = 20;            // modifies x directly, with no star
cout << x;         // prints 20
AspectPointerReference
Must be initialized at declarationnoyes, mandatory
Can be redirected to something elseyesno, never
Can be NULLyesno
Syntax when used*pr (like an ordinary variable)
Occupies its own memoryyesusually not

9Common mistakes

1. An uninitialized pointer int *p; *p = 5; writes to a random address - the program crashes or, worse, corrupts other data. Always initialize: int *p = NULL;
2. Dereferencing a NULL pointer Check before use: if (p != NULL) { *p = 5; }
3. A dangling pointer Returning the address of a local variable is a serious mistake: the variable is destroyed on exiting the function, and the address becomes invalid. int* wrong() { int x = 5; return &x; }
4. Multiple declarations on the same line int *p, q; declares one pointer and one ordinary integer, not two pointers. The star applies to each variable separately: int *p, *q;

10Source code

basics.c - the & and * operators
#include <stdio.h>

int main(void)
{
    int x = 42;
    int *p = &x;              // p receives the address of x

    printf("x       = %d\n",  x);      // 42
    printf("&x      = %p\n",  (void*)&x);
    printf("p       = %p\n",  (void*)p);    // the same address
    printf("*p      = %d\n",  *p);     // 42 - the value at that address
    printf("&p      = %p\n",  (void*)&p);  // where the pointer itself is stored

    *p = 100;                 // modifies x through the pointer
    printf("\nafter *p = 100, x = %d\n", x);   // 100

    printf("sizeof(x) = %zu, sizeof(p) = %zu\n", sizeof(x), sizeof(p));
    return 0;
}
arithmetic.c - traversing an array
#include <stdio.h>

int main(void)
{
    int v[5] = {10, 20, 30, 40, 50};
    int *p = v;                   // equivalent to &v[0]

    printf("By index:   ");
    for (int i = 0; i < 5; i++) printf("%d ", v[i]);

    printf("\nBy pointer: ");
    for (int i = 0; i < 5; i++) printf("%d ", *(p + i));

    printf("\nWith increment: ");
    p = v;
    for (int i = 0; i < 5; i++) printf("%d ", *p++);

    // addresses increase by 4 each time
    printf("\n\nAddresses:\n");
    for (int i = 0; i < 5; i++)
        printf("  &v[%d] = %p\n", i, (void*)&v[i]);

    // the difference of two pointers = the number of elements between them
    int *start = &v[0], *end = &v[4];
    printf("\nElements between start and end: %ld\n", end - start);

    return 0;
}
swap.c - why pointers are needed
#include <stdio.h>

void swapWrong(int a, int b)      // receives COPIES
{
    int tmp = a; a = b; b = tmp;  // swaps only the copies
}

void swapCorrect(int *a, int *b)  // receives ADDRESSES
{
    int tmp = *a; *a = *b; *b = tmp;
}

int main(void)
{
    int x = 1, y = 2;

    swapWrong(x, y);
    printf("After swapWrong:   x=%d y=%d\n", x, y);   // 1 2 - unchanged

    swapCorrect(&x, &y);
    printf("After swapCorrect: x=%d y=%d\n", x, y);   // 2 1 - swapped

    return 0;
}
references.cpp - the C++ version
#include <iostream>
using namespace std;

void swapRef(int &a, int &b)       // references: clean syntax
{
    int tmp = a; a = b; b = tmp;   // no star
}

void addTo(int &value, int amount)
{
    value += amount;
}

int main()
{
    int x = 1, y = 2;

    swapRef(x, y);
    cout << "x=" << x << " y=" << y << endl;   // 2 1

    addTo(x, 10);
    cout << "x after adding = " << x << endl;   // 12

    // a reference cannot be redirected
    int a = 5, b = 9;
    int &r = a;
    r = b;              // NOTE: copies the value of b into a,
                        // does NOT make r refer to b
    cout << "a=" << a << " b=" << b << endl;    // 9 9

    return 0;
}

11Code workshop

Here Step by step mode makes the difference: the panel on the right shows the address of each variable and, for pointers, exactly what they point to at that moment.

A pointer walking through an array
#include <stdio.h>

int main(void)
{
    int v[4] = {10, 20, 30, 40};
    int *p;

    p = v;                    /* p points to v[0] */
    printf("*p       = %d\n", *p);

    p = p + 2;                /* +2 means 2 elements, not 2 bytes */
    printf("after p+2 : *p = %d\n", *p);

    *p = 99;                  /* modifies v[2] through the pointer */
    printf("v[2]     = %d\n", v[2]);

    printf("p - v    = %d elements\n", (int)(p - v));
    printf("sizeof(int) = %d bytes\n", (int)sizeof(int));
    return 0;
}
Try thisRun it step by step and watch the p row in the panel on the right: the address increases by 8 (that is, 2 x 4 bytes), and the label on the right changes from v[0] to v[2].
Exercise - modifying through pointers
#include <stdio.h>

/* This function does NOT work: it receives copies of the values */
void swapWrong(int a, int b)
{
    int t = a; a = b; b = t;
}

/* Write the correct version here, which receives the addresses:
   void swapCorrect(int *a, int *b) { ... }                */

int main(void)
{
    int x = 1, y = 2;

    swapWrong(x, y);
    printf("after swapWrong   : x = %d, y = %d\n", x, y);

    /* Call swapCorrect(&x, &y); */

    printf("after swapCorrect : x = %d, y = %d\n", x, y);
    return 0;
}

12Work tasks

  • Declare a variable and a pointer to it; print the value, the address, and the result of dereferencing.
  • Modify the variable exclusively through the pointer and check the effect.
  • Compare sizeof for an int, a double, and the pointers to them.
  • Traverse an array three ways: with an index, with *(p+i), and with *p++.
  • Print the addresses of the elements and check that the difference is sizeof(type).
  • Implement the swap function, first incorrectly (by value), then correctly (by pointer).
  • Write a function that receives a vector and returns both the minimum and the maximum through pointers.
  • In C++, rewrite the swap using references and compare the readability of the code.

13Extended application

ExtensionWrite a function that receives an array of integers and returns, through a single call, four results: the sum, the mean, the minimum, and the maximum - using only pointer-type parameters. Then extend the program with an array of function pointers (int (*operations[4])(int, int)) that lets you select the arithmetic operation from a menu.

14Review questions

15Resources