An ownership convention decides which function is responsible for freeing heap storage.

owner The owner of a heap pointer is responsible for calling `free`.
transfer Passing a pointer to a consuming function can transfer that responsibility.

Ownership

value
ownership.c
Replay: real traced execution (multi-file project)
#include <stdio.h>
#include <stdlib.h>

int *makeValue(int value) {
    int *ptr = (int *)malloc(sizeof(int));
    if (ptr != 0) {
        *ptr = value;
    }
    return ptr;
}

int consumeValue(int *owned) {
    int result = *owned;
    free(owned);
    return result;
}

int main(void) {
    int value = 6;
    int *owned = makeValue(value);

    if (owned == 0) {
        return 1;
    }

    int result = consumeValue(owned);
    owned = 0;

    printf("result=%d owned=%d\n", result, owned == 0);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int *makeValue(int value) {
    int *ptr = (int *)malloc(sizeof(int));
    if (ptr != 0) {
        *ptr = value;
    }
    return ptr;
}

int consumeValue(int *owned) {
    int result = *owned;
    free(owned);
    return result;
}

int main(void) {
    int value = 9;
    int *owned = makeValue(value);

    if (owned == 0) {
        return 1;
    }

    int result = consumeValue(owned);
    owned = 0;

    printf("result=%d owned=%d\n", result, owned == 0);
    return 0;
}
#include <stdio.h>
#include <stdlib.h>

int *makeValue(int value) {
    int *ptr = (int *)malloc(sizeof(int));
    if (ptr != 0) {
        *ptr = value;
    }
    return ptr;
}

int consumeValue(int *owned) {
    int result = *owned;
    free(owned);
    return result;
}

int main(void) {
    int value = 12;
    int *owned = makeValue(value);

    if (owned == 0) {
        return 1;
    }

    int result = consumeValue(owned);
    owned = 0;

    printf("result=%d owned=%d\n", result, owned == 0);
    return 0;
}
  1. value ← 6

    18int main(void) {19    int value→ 6 = 6; //@value=9, 1220    int *owned = makeValue(value6);
  2. ptr ← ⟨addr A⟩

    4int *makeValue(int value6) {5    int *ptr→ ⟨addr A⟩ = (int *)malloc(sizeof(int));6    if (ptr != 0) {
  3. if (ptr != 0)

    5int *ptr = (int *)malloc(sizeof(int));6if (ptr⟨addr A⟩ != 0) {7    *ptr⟨addr A⟩ = value6;8}
  4. return ptr;

    8    }9    return ptr⟨addr A⟩;10}
  5. owned ← ⟨addr A⟩

    19int value = 6; //@value=9, 1220int *owned→ ⟨addr A⟩ = makeValue(value6);2122if (owned == 0) {23    return 1;24}2526int result = consumeValue(owned⟨addr A⟩);27owned = 0;
  6. result ← 6

    12int consumeValue(int *owned⟨addr A⟩) {13    int result→ 6 = *owned⟨addr A⟩;14    free(owned⟨addr A⟩);15    return result6;16}
  7. result ← 6, owned ← 0

    26    int result→ 6 = consumeValue(owned⟨addr A⟩);27    owned→ 0 = 0;2829    printf("result=%d owned=%d\n", result6, owned0 == 0);30    return 0;31}
    outputresult=6 owned=1
  1. value ← 9

    18int main(void) {19    int value→ 9 = 9;20    int *owned = makeValue(value9);
  2. ptr ← ⟨addr A⟩

    4int *makeValue(int value9) {5    int *ptr→ ⟨addr A⟩ = (int *)malloc(sizeof(int));6    if (ptr != 0) {
  3. if (ptr != 0)

    5int *ptr = (int *)malloc(sizeof(int));6if (ptr⟨addr A⟩ != 0) {7    *ptr⟨addr A⟩ = value9;8}
  4. return ptr;

    8    }9    return ptr⟨addr A⟩;10}
  5. owned ← ⟨addr A⟩

    19int value = 9;20int *owned→ ⟨addr A⟩ = makeValue(value9);2122if (owned == 0) {23    return 1;24}2526int result = consumeValue(owned⟨addr A⟩);27owned = 0;
  6. result ← 9

    12int consumeValue(int *owned⟨addr A⟩) {13    int result→ 9 = *owned⟨addr A⟩;14    free(owned⟨addr A⟩);15    return result9;16}
  7. result ← 9, owned ← 0

    26    int result→ 9 = consumeValue(owned⟨addr A⟩);27    owned→ 0 = 0;2829    printf("result=%d owned=%d\n", result9, owned0 == 0);30    return 0;31}
    outputresult=9 owned=1
  1. value ← 12

    18int main(void) {19    int value→ 12 = 12;20    int *owned = makeValue(value12);
  2. ptr ← ⟨addr A⟩

    4int *makeValue(int value12) {5    int *ptr→ ⟨addr A⟩ = (int *)malloc(sizeof(int));6    if (ptr != 0) {
  3. if (ptr != 0)

    5int *ptr = (int *)malloc(sizeof(int));6if (ptr⟨addr A⟩ != 0) {7    *ptr⟨addr A⟩ = value12;8}
  4. return ptr;

    8    }9    return ptr⟨addr A⟩;10}
  5. owned ← ⟨addr A⟩

    19int value = 12;20int *owned→ ⟨addr A⟩ = makeValue(value12);2122if (owned == 0) {23    return 1;24}2526int result = consumeValue(owned⟨addr A⟩);27owned = 0;
  6. result ← 12

    12int consumeValue(int *owned⟨addr A⟩) {13    int result→ 12 = *owned⟨addr A⟩;14    free(owned⟨addr A⟩);15    return result12;16}
  7. result ← 12, owned ← 0

    26    int result→ 12 = consumeValue(owned⟨addr A⟩);27    owned→ 0 = 0;2829    printf("result=%d owned=%d\n", result12, owned0 == 0);30    return 0;31}
    outputresult=12 owned=1

Follow the Owner

  1. value starts at 6.
  2. makeValue(value) allocates one int on the heap and stores 6.
  3. owned points at that heap value, so main is responsible for it.
  4. consumeValue(owned) reads 6, calls free(owned), and returns 6.
  5. main sets owned = 0, so the final print is result=6 owned=1. | value | heap value made | function that frees it | result | owned == 0 | | ---: | ---: | --- | ---: | ---: | | 6 | 6 | consumeValue | 6 | 1 | | 9 | 9 | consumeValue | 9 | 1 | | 12 | 12 | consumeValue | 12 | 1 |

Exercise: ownership.c

Reproduce result=6 owned=1, then use values 9 and 12 to predict result=9 owned=1 and result=12 owned=1.