OOP Search

Week 02 / 14 Object-Oriented Programming

Review - pointers and dynamic memory

Pointers and pointer arithmetic, const with pointers, arrays and matrices, reading and writing with cin and cout, new and delete, memory leaks and dangling pointers.

Objectives
  • Use the address-of and dereference operators and nullptr correctly
  • Explain what p + 1 means and why p[i] is the same as *(p + i)
  • Tell a pointer to const, a const pointer and a const pointer to const apart
  • Work with static arrays and matrices and pass them to functions
  • Allocate and release memory with new / delete and new[] / delete[]
  • Recognise a memory leak and a dangling pointer, and say who owns a block of memory

Pointers: address and value

A pointer is a variable that holds an address. Two operators do all the work:

int a = 5;
int* p = &a;   // p holds the address of a
*p = 8;        // writes into a
int* q = nullptr;   // points nowhere, on purpose

A pointer that does not point anywhere yet should be nullptr. Never dereference it: *q stops the program.

Pointer arithmetic

Adding 1 to a pointer moves it to the next element, not the next byte. The compiler multiplies by the size of the type for you. That is why p[i] and *(p + i) are the same thing.

#include <iostream>
using namespace std;
int main() {
    int v[5] = {10, 20, 30, 40, 50};
    int* p = v;
    p += 2;
    cout << *p << " " << *(p + 1) << " " << p[-1] << endl;
    cout << p - v;
}

Subtracting two pointers into the same array gives the number of elements between them. Comparing or subtracting pointers into different arrays has no meaning.

const with pointers

Read the declaration from right to left. const applies to what is directly to its left (or to the right, if it is first).

DeclarationChange *p?Change p?
const int* p (pointer to const)NoYes
int* const p (const pointer)YesNo
const int* const pNoNo
int a = 1, b = 2;
const int* p1 = &a;   p1 = &b;   // OK      *p1 = 3;  // error
int* const p2 = &a;   *p2 = 5;   // OK      p2 = &b;  // error
const int* const p3 = &a;        // *p3 = 3; and p3 = &b; are both errors

Arrays and matrices

The name of a static array behaves like a const pointer to its first element: v[i] works, v = other does not compile. When you pass an array to a function, only the address is copied, so the size must travel as a separate parameter:

int sumArray(const int v[], int n);   // same as const int* v

A matrix int m[2][3] is stored row by row, as 6 consecutive ints.

#include <iostream>
using namespace std;
int main() {
    int m[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int s = 0;
    for (int i = 0; i < 2; i++) s += m[i][2];
    cout << s << " " << m[1][0];
}

cin, cout and new[]

When the size is known only at runtime, allocate on the heap with new[] and release with delete[]. Run it, then try a different input.

#include <iostream>
using namespace std;
int main() {
  int n;
  cin >> n;
  int* v = new int[n];
  for (int i = 0; i < n; i++) cin >> v[i];
  int s = 0;
  for (int i = 0; i < n; i++) s += v[i];
  cout << "sum: " << s << endl;   // expected for input 4 7 9 10 6: sum: 32
  delete[] v;
  v = nullptr;
}
Input (stdin)

Open in Godbolt

new / delete and new[] / delete[]

Every allocation has exactly one matching release:

AllocateRelease
new int(7)delete p;
new int[n]delete[] p;
malloc(...)free(p);

Mixing them (delete on a new[] block, free on a new block) is undefined behaviour. So is releasing the same block twice.

STACK
v: ●
HEAP
[7][9][10]

int* v = new int[3]{7, 9, 10}; - v is on the stack, the three ints are on the heap.

Step 1/3

Memory leaks

A leak is a heap block whose address you no longer have. Nobody can release it.

int* p = new int[10];
p = new int[10];      // the first block is lost: leak

void f() {
    int* t = new int[3];
}                     // t disappears, the block stays: leak

Before you write new, decide who owns the block, that is, who will call delete. If a function returns a pointer to new memory, the caller owns it and the comment above the function should say so.

Dangling pointers

A dangling pointer holds an address where there is no longer a valid object. Two common ways to get one:

int* createValue() {
    int value = 10;
    return &value;    // wrong: value dies when the function returns
}

int* p = new int(5);
delete p;
*p = 6;               // wrong: the block was released

g++ warns about the first one (address of local variable returned). Reading through a dangling pointer is undefined behaviour: it may print the old value, garbage, or crash.

Check yourself

1. Given int a = 1; const int* p = &a;, what can you NOT do?

2. Given int a = 1, b = 2; int* const p = &a;, which line does NOT compile?

3. What does the program print?
#include <iostream>
using namespace std;
int main() {
    int v[4] = {1, 2, 3, 4};
    int* p = v + 1;
    *(p + 1) = 10;
    p++;
    cout << *p << " " << v[2] << " " << p[-2];
}

4. How do you release the memory from int* v = new int[n];?

5. Which of these leak memory?

6. What does the program print?
#include <iostream>
using namespace std;
void grow(int*& p, int& n) {
    int* q = new int[n + 1];
    for (int i = 0; i < n; i++) q[i] = p[i];
    q[n] = 100;
    delete[] p;
    p = q;
    n++;
}
int main() {
    int n = 2;
    int* v = new int[n]{5, 6};
    grow(v, n);
    cout << n << " " << v[0] + v[2];
    delete[] v;
}

7. What is wrong with int* createValue() { int value = 10; return &value; }?

Score: 0/7

Code in repository: L02

Romanian version