Skip to content
academia.sh

Lesson 15 / 15

Portability

Three values the shared core deliberately never printed — addresses, type sizes other than char, malloc(0)'s return — are target-dependent; this lesson isolates target-dependent behavior by printing a comparison the standard guarantees instead of a value, and gathers the course's fifteen lessons into a closing table.

Published Updated

In this lesson

  • Distinguishes why an address, a type's size, and a zero-size allocation were never printed
  • Isolates target-dependent behavior by printing a comparison the standard guarantees instead of a value
  • Gathers the course's fifteen lessons into a single table by ownership class
Contents

The previous lesson measured that the limit parameter is written in the caller, not the source. This lesson closes the course itself and leaves behind a single question: what the measurement core never printed from the start. ownership.c printed thirteen observations, but it never printed three things — addresses, type sizes other than sizeof(char), and whether malloc(0) returns null. All three were left out for the same reason: all three are target-dependent, meaning the same source text can produce two different numbers on two different targets.

This lesson does not try to print those three values. Instead it prints a comparison the standard guarantees. The difference is this: printing sizeof(int) gives a number, and that number changes by target; asking the question sizeof(char) == 1 also gives a result, but this result is the same on every target, because the standard itself guarantees it. What is measured is not two approaches to the same question — one exposes a target-dependent value, the other exposes the target-independent logical result the standard leaves fixed. This lesson’s ownership class is the language: the party giving the guarantee is not a caller, a callee, or a block left unowned, it is the language’s standard itself.

Three Comparisons, One Isolation Technique

The measurement core’s counter unit is also used in this lesson; its excerpt is below, unchanged.

// counter.h — a wrapper that counts every allocation and every free
#ifndef COUNTER_H
#define COUNTER_H

#include <stddef.h>

void *count_alloc(size_t size);
void *count_grow(void *old, size_t size);
void count_free(void *block);
int allocated_count(void);
int freed_count(void);

#endif
// counter.c — the counters live only in this unit
#include <stdlib.h>
#include "counter.h"

static int allocated = 0;
static int freed = 0;

void *count_alloc(size_t size) {
    void *block = malloc(size);
    if (block != NULL) {
        allocated++;
    }
    return block;
}

void *count_grow(void *old, size_t size) {
    int new_block = (old == NULL);
    void *grown = realloc(old, size);
    if (grown != NULL && new_block) {
        allocated++;
    }
    return grown;
}

void count_free(void *block) {
    if (block != NULL) {
        freed++;
    }
    free(block);
}

int allocated_count(void) {
    return allocated;
}

int freed_count(void) {
    return freed;
}

RO10. counter.c keeps the same behavior in this lesson too: count_alloc increments its counter only if the return is not NULL, count_free increments its counter only if the argument is not NULL and still calls free regardless. These two lines are set up in advance for this lesson’s last observation — even though what malloc returns when size is zero is target-dependent, the behavior of these two lines in counter.c does not change.

The new file measures the three comparisons and a zero-size allocation together. No line prints a size, a limit value, or an address; every line only prints a comparison’s result.

// portability.c — prints the standard's guarantee instead of a value
#include <stdint.h>
#include <limits.h>
#include <stdio.h>
#include "counter.h"

static void observe(const char *name, const char *result) {
    printf("%-34s%s\n", name, result);
}

int main(void) {
    printf("%-34s%s\n", "observation", "result");

    observe("fixed-width integer", sizeof(int32_t) * CHAR_BIT == 32 ? "guarantee holds" : "guarantee does not hold");
    observe("char size", sizeof(char) == 1 ? "guarantee holds" : "guarantee does not hold");
    observe("int lower bound", INT_MAX >= 32767 ? "guarantee holds" : "guarantee does not hold");

    int first = 7;
    int second = 7;
    int *same = &first;
    int *again = &first;
    int *different = &second;
    observe("two addresses, one variable", same == again ? "equal" : "not equal");
    observe("two addresses, two variables", same == different ? "equal" : "not equal");

    int before = allocated_count() - freed_count();
    void *zero_size = count_alloc(0);
    count_free(zero_size);
    int after = allocated_count() - freed_count();
    observe("zero-size allocation contract", after - before == 0 ? "contract held" : "contract broken");

    return 0;
}
observation                       result
fixed-width integer               guarantee holds
char size                         guarantee holds
int lower bound                   guarantee holds
two addresses, one variable       equal
two addresses, two variables      not equal
zero-size allocation contract     contract held

RO11. The first three lines follow the same pattern: instead of directly printing a type’s size or a limit value, each compares it against a lower bound the standard already guarantees. When int32_t exists it is exactly thirty-two bits wide — this comes from the definition itself, not from the target. This is also why the comparison is built on bit count rather than byte count: the number of bits in a byte (CHAR_BIT) is guaranteed by the standard only to be at least eight, so sizeof(int32_t) can vary from target to target, while sizeof(int32_t) * CHAR_BIT does not. sizeof(char) is always one — this too comes from the definition; this is exactly why no size other than sizeof(char) has been printed anywhere in the course. INT_MAX is guaranteed by the standard to be at least 32767; its real value may be larger depending on the target, but whether this lower bound holds gives the same result on every target.

RO12. The fourth and fifth lines measure pointer equality without printing addresses. same and again hold the address of the same variable; their equality comes from a language rule — the same expression (&first) gives the same result every time it is evaluated. same and different, on the other hand, hold the addresses of two separate automatic objects alive at the same time; the standard guarantees that these two objects will not overlap, so the inequality too is not an implementation coincidence, it is a language guarantee. Both lines measure these two guarantees without ever showing the address itself.

RO13. The last line takes up the shared core’s third unprinted value: whether malloc(0) returns null is target-dependent, and this lesson does not answer that question. Instead it measures whether counter.c’s two lines are affected by that ambiguity. If count_alloc(0) returns NULL, the counter never increments and count_free(NULL) never increments either — the difference stays zero. If count_alloc(0) returns a valid pointer, the counter increments by one and the counter increments by one again as count_free frees it — the difference still stays zero. Which branch malloc(0) took is never visible; the only thing visible is that the contract stays the same on both branches.

The Fixed-Width Types’ Own Limit

The isolation <stdint.h> offers is not unlimited by itself. The standard requires full-width types like int32_t to be defined only if the target platform has such a type; in its absence these types are not defined at all, and a source file using them does not compile. The standard gives the types it requires to exist on every target separate names — int_least32_t guarantees being at least thirty-two bits wide, int32_t guarantees being exactly thirty-two bits wide, but only when it exists.

RO14. That this lesson uses int32_t directly instead of testing for its existence is an assumption made by the lesson itself: this type exists in the environment where the lesson is compiled. The isolation technique itself thus also rests on an assumption — not about the width, but about the type’s existence. Where the exact width is not required, a portable source uses int_least32_t or int_fast32_t; this lesson chose int32_t only as an example.

The Bounding Measurement

Printing a guarantee instead of a value does not eliminate what is target-dependent — it only moves it outside the observation. The expression sizeof(int32_t) * CHAR_BIT == 32 gives the same result on every target, but only when int32_t exists; the standard itself does not require this type to exist. Likewise INT_MAX >= 32767 holds on every target, but the real INT_MAX value can vary from target to target — the lesson preserves portability by never printing this value, but leaves the actual question it wanted to test (exactly what INT_MAX is) unanswered. A comparison is more portable than a value, but it carries less information; this trade-off between portability and information marks where the language’s guarantee ends. None of the measurement core’s four classes eliminates this trade-off — including the language class, because the language that gives the guarantee also draws the guarantee’s own boundary.

Summary

  • The memory class this lesson measures is the language: the party giving the guarantee is not a caller, a callee, or a block left unowned, it is the language’s standard itself.
  • Three values the shared core deliberately never printed — addresses, type sizes other than sizeof(char), malloc(0)’s return — are all target-dependent, and none of them were printed in this lesson either.
  • When a comparison the standard guarantees is printed instead of a value, the result stays the same on every target; sizeof(int32_t) * CHAR_BIT == 32, sizeof(char) == 1, and INT_MAX >= 32767 all followed this pattern.
  • The addresses of two automatic objects alive at the same time do not overlap; this inequality was measured only with the == comparison, never by printing the address itself.
  • Whether malloc(0) returns null stays target-dependent, but counter.c’s allocate-free contract was unaffected by that ambiguity: the difference stayed zero on both branches.
  • Printing a guarantee does not eliminate what is target-dependent, it only moves it outside the observation — int32_t itself is a type that may not exist.

Course Wrap-Up

Throughout the course, a single question was asked: who owns this memory. Fourteen lessons answered that question with four classes — language, caller, callee, unowned — and this lesson showed what the same question says when asked of the language itself: where the standard guarantees something, the language owns it; where the guarantee ends, the observation stays target-dependent and is not printed.

Lesson Memory measured Owner Bounding measurement
Pointer Basics Local variable, static storage, string literal, local array language A pointer’s type does not say who will free it
Pointer Arithmetic In-array advance and element difference language Pointing one past the end is valid, dereferencing it is undefined
Arrays and Pointers Dynamically allocated array caller Decay works at only one level, it does not carry the element count
Null and Dangling Pointers Pointer pointing at a freed block unowned Assigning NULL only makes that one pointer checkable, it does not affect another pointer pointing at the same block
void and Opaque Pointers Opaque record (Record) callee Opacity hides the struct’s inside, not its ownership: the obligation to free is written only in the comment
Function Pointers Context carried into a callback caller A signature guarantees no behavior, it only checks type compatibility
Memory Allocation Functions Block allocated with malloc/calloc/realloc caller malloc(0) and realloc(block, 0)’s return is implementation-defined, never printed
Deallocation Discipline Lent name and returned copy callee Ownership is read not from the signature but from the comment in the header file
Memory Leaks Two unowned blocks unowned A leak produces no crash or warning, it only shows up as a counter difference
Memory Debugging Tools Balanced and unbalanced allocate-free pair unowned A tool only counts the unmatched call, it does not read the contract violation
Memory Layout Variable in one of four memory regions language Region boundaries and alignment are not printed; outside the heap, ownership comes from the contract, not the region
Undefined Behavior Defined counterpart of four undefined operations unowned Looking like it works does not show that it is defined
Error Handling Patterns Block cleaned up on the error path callee Who cleans up on partial failure must be written separately in every function
Bounds Checking Copy bounded with snprintf caller The function only checks the limit it is told, it cannot catch a wrongly reported limit
Portability Fixed-width type, sizeof(char), INT_MAX, zero-size allocation language The isolation technique itself rests on a guarantee too: int32_t’s existence is not required by the standard

The lessons are distributed across classes as 4 language · 4 caller · 3 callee · 4 unowned — this is the lessons’ distribution and is not to be confused with the shared core’s thirteen observations’ 4/4/3/2.

The four classes never changed across fifteen lessons, but each lesson showed the class through a different face: sometimes the owning party was a function, sometimes a contract, sometimes nobody at all, and sometimes, as seen in this last lesson, the language itself. A pointer’s type never once told its owner; the owner was decided every time either by the source itself (an & expression, an allocation call) or by the contract written in the header file.

The next course turns to C and systems programming, to operating-system interfaces. This course’s ownership question keeps holding there too — who owns a file descriptor, a process, or a signal is shaped in the same form as the question asked for memory — but that course will establish its own resources’ classes with its own measurement.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close