Course outline

C / Intermediate: Memory and Reliable Components / Lesson 9

Structures and Data Models

Group related values without confusing an in-memory representation with ownership, equality, or an external file format.

About 5 min readContent reviewed
On this page

Lesson goals

  • Maintain invariants across structure fields.
  • Explain structure assignment and shallow pointer copies.
  • Use enums and tagged unions to represent alternatives.

Fields form one logical record

A struct groups named members, each with its own type. The compiler may insert padding between members and at the end to meet alignment requirements. The logical record consists of member values, not a promise that the bytes have a compact or universal layout. Use member names to describe the domain rather than relying on positional assumptions.

A useful model states relationships between fields. For an interval, lower must not exceed upper. For a buffer, length must not exceed capacity, and a nonzero capacity requires valid storage. The constructor and every mutating operation must preserve those relationships; exposing fields publicly makes all callers responsible for doing so.

Work with a value record

The first example uses a structure containing only values. Assignment copies those member values into a second record; changing the copy’s x member does not change the original. Designated initializers tie values to member names, making intent clearer when the declaration order changes. Members omitted by an initializer are initialized according to the usual aggregate initialization rules.

For a record with embedded fixed-size arrays, structure assignment copies their stored elements as part of the value. This differs from copying a structure that merely contains pointers to external allocations. The latter copies pointer values and can create shared aliases, so its ownership contract requires separate thought.

c Copy a value record
#include <stdio.h>

struct point { int x; int y; };

int main(void)
{
    struct point first = {.x = 2, .y = 5};
    struct point copy = first;
    copy.x = 9;
    printf("%d,%d %d,%d\n", first.x, first.y, copy.x, copy.y);
    return 0;
}
Expected output
2,5 9,5

The two structure objects have independent int members.

Pointer members require a copy policy

If a structure owns char *name, ordinary assignment duplicates the address, not the allocated characters. Treating both copies as exclusive owners would lead to double release. Choose among a noncopyable ownership convention, an explicit deep-copy operation, shared ownership with a correct reference protocol, or a borrowed-view type with no release responsibility.

C does not generate constructors or destructors. Define initialization and destruction functions, document whether destruction accepts an empty object, and reset fields to the neutral invariant when useful. A typedef can shorten a type name but does not add ownership behavior or make accidental assignment safe.

Represent one active alternative

A union shares storage among its members. A tagged union adds an enum saying which member the program currently treats as active. The tag and payload must be updated together under the type’s invariant. This makes a value that is either an integer or a real number explicit, instead of using a magic numeric sentinel.

The example writes the integer member and selects it only after checking the tag. For portable application logic, do not use an unrelated union member as a general conversion trick; representation reinterpretation has restrictions and can expose trap or unsuitable representations. Converting a value explicitly communicates a different and usually clearer operation.

c Inspect a tagged value
#include <stdio.h>

enum kind { INTEGER_VALUE, REAL_VALUE };
struct number {
    enum kind tag;
    union { int integer; double real; } data;
};

int main(void)
{
    struct number value = {.tag = INTEGER_VALUE, .data.integer = 7};
    if (value.tag == INTEGER_VALUE) printf("integer=%d\n", value.data.integer);
    else if (value.tag == REAL_VALUE) printf("real=%.1f\n", value.data.real);
    return 0;
}
Expected output
integer=7

The tag selects the member initialized with the integer seven.

Serialize meaning, not raw padding

Writing sizeof(struct record) bytes directly to a file couples the format to padding, byte order, integer representation, and compiler ABI choices. It may also expose uninitialized padding. Define external fields and their encodings explicitly, then encode and decode each field with range and length checks. A text format can be simpler when size and speed do not dominate.

Equality is similarly semantic. memcmp can disagree for logically equal structures because padding bytes need not match. Compare the meaningful members under the domain’s equality rule. For a floating field, that rule must also address approximation and special values rather than inheriting a bytewise comparison accidentally.

Common errors and fixes

Copying a record later causes a double free.

Why it happens
Pointer members were shallow-copied into two apparent owners.
How to fix it
Implement an explicit copy or transfer policy.
Prevent it next time
Separate owning records from borrowed views in interfaces.

A saved structure cannot be read on another target.

Why it happens
Raw layout was mistaken for a portable format.
How to fix it
Encode fields with a specified byte order and widths.
Prevent it next time
Document serialization independently from the in-memory type.

The tag says real but the code reads integer.

Why it happens
Tag and payload invariants were not enforced.
How to fix it
Switch on the tag and access only its documented alternative.
Prevent it next time
Centralize constructors and test each variant.

Check your understanding

1. A record owns a dynamically allocated label and a count. Design what a deep copy must do if allocating the new label fails.

Show worked solution

Keep the destination in an empty valid state. Determine and check the label size, allocate new storage, and copy the complete label only after allocation succeeds. Copy the count as a value. If allocation fails, return failure without transferring the source’s pointer or changing the source. The destination must not become a second owner of the original label.

2. Why can two equal point records fail a bytewise equality test on a hypothetical implementation? Provide the correct comparison.

Show worked solution

Padding bytes may differ even when x and y match. Compare first.x == second.x && first.y == second.y to express value equality. This uses only meaningful members and remains valid if the implementation changes padding. If the domain later treats coordinates approximately, update the semantic comparison deliberately rather than switching to raw byte equality.

Key takeaways

  • A structure’s invariant connects its fields and its ownership rules.
  • Assignment copies pointer values without duplicating their allocations.
  • External formats and semantic equality should be defined independently of padding.

Official references