SEN2001 · Week 5 · Data Types

Data types II: record, union, pointer and type equivalence

Records and tuples, free and discriminated unions, pointers/references, dangling pointers and memory leaks, heap management, type checking, strong typing and name/structure type equivalence.

recordfieldtupleunionfree uniondiscriminated uniontagpointerreference typedereferencingpointer arithmeticdangling pointerlost heap-dynamic variablememory leaktombstonelocks-and-keysreference countingmark-sweepgarbage collectiontype checkingtype errorstrongly typedname type equivalencestructure type equivalence

The previous page was about types that hold single values. This page covers types that bring values together (record, union), types that point to other values (pointer, reference), and the compatibility of types with each other (type checking, type equivalence). The most asked on exams: the difference between a dangling pointer and a memory leak, free and discriminated unions, name and structure equivalence.

Golden rules:

  1. Dangling pointer: a pointer that points to a deleted cell. Leak: a cell that nobody points to.
  2. A free union is not checked, a discriminated union carries a tag.
  3. Name equivalence = same name, structure equivalence = same structure.
  4. Reference counting cannot collect cycles.

Record

A record is a collection of heterogeneous elements (fields) accessed by their names. Unlike an array, the elements can be of different types; access is by name, not index: emp.name. COBOL and C struct, classes and record in Java.

record P(int x, int y) {}
P p = new P(1, 2), q = new P(1, 2);
System.out.println(p.equals(q) + " " + (p == q));
System.out.println(p);
true false
P[x=1, y=2]

Field access is translated at compile time into a fixed offset, which is why it is fast. A tuple is similar to a record but the fields have no names (Python (3, "a"), ML); access is by position.

Union

A union is a type in which a variable can hold values of different types at different times during execution. All fields share the same memory.

  • Free union (C, C++): nothing keeps track of which field is valid. No type checking.
  • Discriminated union (Ada, ML, Haskell): a tag (discriminant) field says which type is valid; reading the wrong field is an error.
union { int i; float f; } u;
u.i = 1;
printf("%g", u.f);   /* the bits of the int are read as a float */

The float interpretation of the same bits can be shown with Java:

System.out.println(Float.intBitsToFloat(1));
System.out.println(Float.intBitsToFloat(1065353216));
System.out.println(Float.floatToIntBits(1.0f));
1.4E-45
1.0
1065353216

The integer 1 as a float is 1.4E-45; the bits of 1.0f as an int are 1065353216. A free union makes this confusion silently. Java and C# did not take unions at all; for reliability, inheritance is used.

Pointer and reference

A pointer holds a memory address or the special value nil (null). It has two uses: indirect addressing and dynamic memory management on the heap.

OperationC/C++Meaning
Assignmentp = &x;p holds the address of x
Dereference*pthe value in the cell p points to
Arithmeticp + 1The next element; the address increases by sizeof(type)
Field accessp->x(*p).x
int a[5] = {10, 20, 30, 40, 50};
int *p = a;     /* p = &a[0] */
p += 2;         /* p = &a[2] */
/* *p + 1 = 31,  *(p + 1) = 40,  p - a = 2 */

A reference type (C++ int&, Java and C# class variables) points to an object but arithmetic cannot be done. In Java, references never point to freed memory, because freeing is the job of the garbage collector. That is why there are no dangling references in Java.

Pointer problems

Dangling pointer

A pointer that still holds the address of a freed (deallocated) heap cell.

int *p = new int(5);
int *q = p;
delete p;      // p and q now point to the freed cell

You can set p to nullptr, but you forget q. If that cell is later given to another variable, writing through q overwrites other data.

Lost heap-dynamic variable (memory leak)

An allocated heap cell that no pointer can reach anymore. These are called garbage.

int *p = new int;   // cell 1
p = new int;        // cell 2; cell 1 is LOST
int *q = p;         // q points to cell 2
p = new int;        // cell 3; cell 2 was saved thanks to q

The number of lost cells is 1. On an exam, number every new, and at the end write which pointer points to each cell. If nobody points to it, it is a leak; if something points to it but the cell is deleted, it is dangling.

Dangling pointer solutions

  • Tombstone: an extra cell for each heap object; pointers point to this cell. When the object is deleted, the tombstone becomes nil. Cost: time and memory.
  • Locks-and-keys: a pointer is a (key, address) pair; the heap cell carries a lock. On access key == lock is checked; the lock of a deleted cell is changed.
  • Best: not giving the programmer the power to delete (Java, C#, LISP: garbage collection).

Heap management: garbage collection

MethodHowProCon
Reference counting (eager)Each cell counts how many pointers point to it, freed at 0Collection is immediate, no pausesMemory and time for counters; cannot collect cycles
Mark-sweep (lazy)When memory runs out: first consider everything garbage, mark what is reachable from the root, collect the unmarkedCollects cyclesThe program stops during collection
A.next = B;  B.next = A;  root = A;  root = null;
reference count: A = 1 (from B), B = 1 (from A)  → neither is 0, not collected
mark-sweep: no path from the root to A → both unmarked → collected

Type checking and strong typing

Type checking is checking that the operands of an operator are of compatible types. A compatible type is either directly valid or can be converted automatically by the language’s rules (coercion). Using an operand of an inappropriate type is a type error.

If type bindings are static, checking can be done almost entirely at compile time; with dynamic type binding (JavaScript, Python) checking has to be done at run time. The earlier you catch an error, the cheaper it is to fix.

A strongly typed language: type errors are always caught. ML and F# are strongly typed. Java and C# nearly (an explicit cast can produce an error but it is caught at run time). C and C++ are not: free unions and pointer conversions are outside checking. Too much coercion reduces the benefit of strong typing.

Type equivalence

When are two types considered “the same”?

Name type equivalenceStructure type equivalence
RuleIf declared with the same type nameIf the structure of the types is the same
ImplementationEasyHard (nested structures, ordering, array bounds)
FlexibilityRestrictiveFlexible
SafetyDifferently named types don’t get mixedTypes that should be kept apart can get mixed
Used byAda, Java classesC (except struct and union)
type Celsius = float;
type Fahrenheit = float;
c: Celsius;  f: Fahrenheit;
f := c;       name equivalence: ERROR     structure equivalence: valid

PITFALL · Ada anonymous types. In the line A, B : array (1..10) of Integer;, A and B are not equivalent: an anonymous type expression produces a separate type for each variable. For them to be equivalent, the type must be given a name first.

Common mistakes

WrongCorrect
Dangling = a cell nobody points toThat is a leak; dangling points to a deleted cell
After delete p only p is dangerousEvery pointer holding the same address, like q, is dangling
p + 1 increases the address by 1 byteIt increases by sizeof(type)
A C union is checkedFree union, no checking
Java has unionsIt does not
Reference counting collects everythingIt cannot collect cycles
Celsius = Fahrenheit alwaysNot under name equivalence
Java is not strongly typed because there are castsCast errors are caught at run time; nearly strongly typed

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

What is printed in Java?

class Point { int x, y; Point(int x, int y) { this.x = x; this.y = y; } }
Point a = new Point(1, 2);
Point b = a;
b.x = 5;
System.out.println(a.x);
Show answer
5

In Java, variables of class type are references. b = a does not copy the object; it creates a second name (alias) for the same object. A change made through b is visible from a.

Question 2medium

What is printed in Java (16+)?

record P(int x, int y) {}
P p = new P(1, 2), q = new P(1, 2);
System.out.println(p.equals(q) + " " + (p == q));
Show answer
true false

A record is a record type whose fields are immutable; the compiler generates equals so that it compares field by field. == compares the addresses of two separate objects: false.

Question 3medium

Consider the C++ code. After the last line, which pointers are dangling?

int *p = new int(5);
int *q = p;
delete p;
Show answer
both p and q (q is the hidden one)

delete p frees the cell, but the address inside p and q does not change. Both point to a freed cell: dangling pointers. Setting p to nullptr does not save q; the programmer may have forgotten that q exists. The danger: if that cell is given to another variable, its data gets corrupted through q.

Question 4medium

How many heap cells are lost (memory leak) after this C++ code?

int *p = new int;
p = new int;
int *q = p;
p = new int;
Show answer
1

Cell 1: the one p first pointed to; when p points elsewhere with the second new, no pointer points to it → lost. Cell 2: q still points to it. Cell 3: p points to it. Total 1 lost heap-dynamic variable (garbage).

Question 5medium

In C, int a[5] = {10, 20, 30, 40, 50}; int *p = a; p += 2; What are *p + 1 and *(p + 1)?

Show answer
31 and 40

p += 2 moves the pointer forward by two elements (the address increases by 2 · sizeof(int) = 8 bytes), and p now points to a[2]. *p + 1: first dereference (30), then +1 = 31. *(p + 1): the next element, a[3] = 40.

Question 6easy

A pointer p to int holds address 1000 and int is 4 bytes. What address does p + 3 hold? What if p were a pointer to double (8 bytes)?

Show answer
1012; 1024

Pointer arithmetic is scaled by the size of the pointed-to type: p + k = address + k · sizeof(type). int: 1000 + 12. double: 1000 + 24.

Question 7hard

In C, union { int i; float f; } u; u.i = 1; and then u.f is read. What kind of union is this and what is the problem?

Show answer
free union; no type checking, the int bits are reinterpreted as a float (1.4E-45)

C and C++ unions are free unions: there is no tag that records which field was last written, and the compiler does not check. When the bits of 1 are read as a float, a very small number comes out (in Java Float.intBitsToFloat(1) = 1.4E-45). A discriminated union in Ada and ML carries a tag and catches reading the wrong field. Java and C# have no unions.

Question 8medium

Two types are declared as below. Under name type equivalence and under structure type equivalence, may a variable of type Celsius be assigned to a variable of type Fahrenheit?

type Celsius = float;
type Fahrenheit = float;
Show answer
name: no; structure: yes

Name type equivalence: types are equivalent if their names are the same; Celsius ≠ Fahrenheit. Safe (temperatures cannot be mixed up) but restrictive. Structure type equivalence: types are equivalent if their structure is the same; both are float. Flexible, but it cannot separate types the programmer wants to keep apart, and it is hard to implement.

Question 9medium

Which heap management technique fails to reclaim the two objects that point to each other below, after root is set to null?

A.next = B;  B.next = A;
root = A;
root = null;
Show answer
reference counting

Reference counting keeps in each cell how many pointers point to it, and frees it when the counter reaches 0. When root is gone, A's counter stays 1 (from B), and B's counter stays 1 (from A): neither reaches zero, but neither can be reached from the program. Mark-sweep marks everything reachable from the root and collects the unmarked; it collects the cycle.

Question 10easy

Which dangling-pointer solution adds an extra heap cell between every pointer and its target, and sets it to nil when the target is deallocated?

Show answer
tombstones

In the tombstone method, pointers point not to the object directly but to a tombstone cell. When the object is deleted, the tombstone becomes nil, and nobody can access the deleted memory. The cost: extra memory and an extra indirection on every access. Locks-and-keys: a pointer is a (key, address) pair and the cell carries a lock; on access the key is compared with the lock.

Question 11easy

Which of these languages are strongly typed or nearly so: C, C++, Java, ML? Give one reason why C is not.

Show answer
Java (nearly) and ML; C is not because unions are not type checked

Strongly typed: type errors are always caught (at compile time or run time). ML and F# are strongly typed. Java and C# nearly (explicit casts can produce errors but they are caught at run time). C and C++ are not: free unions and unchecked pointer conversions miss type errors. Coercion rules also weaken strong typing.

Question 12hard

In Ada, A, B : array (1..10) of Integer; declares two variables with an anonymous array type. Are A and B type equivalent?

Show answer
No

In Ada an anonymous type expression creates a separate, unique type for each variable; so A and B are not considered equivalent (A := B does not compile). For them to be equivalent, a named type is defined first: type List is array (1..10) of Integer; A, B : List;. Ada takes name equivalence seriously.

Verification: The Java outputs were run with the JDK (java verify/SEN2001/W05b.java); the bit reinterpretation of a C union was shown on the same bits with Java's Float.intBitsToFloat function. C pointer arithmetic, the lost and dangling counts after new/delete, and the reference counting cycle were computed in node with a small memory simulation (verify/SEN2001/05b.mjs). The C code was not run directly because no C compiler is installed.

Review card

record: heterogeneous, access by field NAME (p.x). tuple: unnamed fields (Python) free union (C/C++): no tag, no checking → unsafe. discriminated (Ada, ML): has a tag Java, C#: NO union pointer: address + nil. *p dereference. p + k = address + k * sizeof(type) reference (Java, C++ &): no arithmetic, safer dangling: points to a freed cell (q after delete p) lost heap-dynamic (leak): an allocated cell nobody points to tombstone: an extra cell in between, nil when deleted. locks-and-keys: key == lock check reference counting: immediate, but CANNOT collect a CYCLE mark-sweep: mark from the root, collect the unmarked; collects the cycle, causes pauses strongly typed: type errors always caught. ML yes, Java nearly, C no name equivalence: same type NAME. structure equivalence: same STRUCTURE Ada anonymous array type: each variable gets a separate type