Skip to content

Latest commit

 

History

History
411 lines (297 loc) · 14.8 KB

File metadata and controls

411 lines (297 loc) · 14.8 KB

API Reference

This reference is organized by operation group and focuses on behavior contracts: purpose, preconditions, failure behavior, and complexity.

Public types and type generation

array_size_t

  • Purpose: public size type for array counts, capacities, indexes, and slice lengths.
  • Definition: alias of size_t.
  • Header dependency: available through array.h, which includes <stddef.h>.
  • Notes: use array_size_t in APIs that interact with Arraylist metadata; cast to size_t when passing to standard-library formatting or APIs that explicitly require size_t.

Array(T)

  • Purpose: owning dynamic array pointer for element type T.
  • Generated by: generate_array_type(T).
  • Shape: pointer to ArrayStruct(T).
  • Public fields:
    • count: initialized element count.
    • capacity: allocated element slots.
    • elements: flexible array member containing element storage.
  • Ownership: must be released with array_free.
  • Invalidation: the pointer value may change after array_reserve or array_try_push, because growth can call realloc.

ArrayStruct(T)

  • Purpose: concrete struct type behind Array(T).
  • Generated by: generate_array_type(T).
  • Shape:
typedef struct {
    array_size_t count;
    array_size_t capacity;
    T elements[];
} ArrayStruct(T);
  • Notes: most call sites should use Array(T) rather than naming ArrayStruct(T) directly.

Slice(T)

  • Purpose: owner-relative half-open range over an Array(T).
  • Generated by: generate_array_type(T) or decl_slice(T).
  • Public fields:
    • start: first array index in the range.
    • count: number of elements in the range.
  • Ownership: stores no pointer and owns no memory.
  • Invalidation: does not dangle when the backing array is reallocated; it remains usable when the current array still has enough elements for start + count.

Span(T)

  • Purpose: temporary raw pointer view over contiguous elements of type T.
  • Generated by: generate_array_type(T) or decl_span(T).
  • Public fields:
    • count: number of elements in the view.
    • elements: borrowed pointer to the first element.
  • Ownership: never pass a span to array_free.
  • Invalidation: a span becomes dangling if the backing array/storage is freed or reallocated.

Type generation

generate_array_type(T)

  • Purpose: declare typed Array(T), Slice(T), and Span(T) forms.
  • Preconditions: run before first use of those generated types.
  • Failure behavior: none (macro expansion).
  • Complexity: compile-time only.

Allocation and lifetime

array_make(T, size)

  • Purpose: allocate a new Array(T) with initial capacity == size and count == 0.
  • Preconditions: valid type T; size convertible to array_size_t.
  • Failure behavior: returns NULL on overflow or allocation failure.
  • Complexity: O(1) allocation step (allocator-dependent).
  • Notes: use size == 8 as the recommended default for ordinary small/unknown lists. Use 0 when avoiding spare allocation matters, and use a larger estimate when one is known.

array_free(arr)

  • Purpose: release array memory.
  • Preconditions: pass the same owning pointer returned by array_make/growth path.
  • Failure behavior: none; free(NULL) is valid.
  • Complexity: O(1) call (allocator-dependent release work).

Growth and insertion

array_reserve(arr, min_capacity) -> bool

  • Purpose: ensure arr->capacity >= min_capacity.
  • Preconditions:
    • arr must be non-NULL.
    • arr must be a modifiable lvalue (may be updated after realloc).
  • Failure behavior: returns false on null input, overflow, or allocation failure.
  • Complexity:
    • O(1) if existing capacity is enough.
    • O(n) when reallocation moves/copies existing elements.

array_try_push(arr, value) -> bool

  • Purpose: append one element at the logical end.
  • Preconditions: arr non-NULL; arr is a modifiable lvalue; value can be assigned to the array element type.
  • Failure behavior: returns false if arr is null, count overflows, or growth fails.
  • Complexity: amortized O(1), worst-case O(n) on resize.
  • Notes: reserves capacity before assigning value into arr->elements[arr->count], so normal C assignment diagnostics catch incompatible element types.

array_try_push_lvalue(arr, value) -> bool

  • Purpose: compatibility alias for array_try_push.
  • Preconditions: same as array_try_push.
  • Failure behavior: same as array_try_push.
  • Complexity: same as array_try_push.

array_push(arr, value) (compatibility)

  • Purpose: append without surfacing failure in call sites.
  • Preconditions: same as array_try_push; caller accepts silent failure.
  • Failure behavior: internally ignores failed push.
  • Complexity: same as array_try_push.

Access and slicing

array_try_at(arr, idx, out_ptr) -> bool

  • Purpose: checked element access by index; writes a pointer to the element inside the array (not a copy).
  • Preconditions:
    • arr non-NULL
    • idx < arr->count
    • out_ptr non-NULL (address of a T* variable)
  • Failure behavior: returns false when any precondition fails; does not write *out_ptr on failure.
  • Complexity: O(1).
  • Notes: the returned pointer is temporary and invalidated if the array is reallocated or freed. Use array_try_get when a copy is enough.

array_at(arr, idx) (unchecked)

  • Purpose: direct index access.
  • Preconditions: arr non-NULL and idx in range.
  • Failure behavior: undefined behavior if preconditions are violated.
  • Complexity: O(1).

array_try_get(arr, idx, out_value) -> bool

  • Purpose: checked element access by index; writes a copy of the element to out_value.
  • Preconditions:
    • arr non-NULL
    • idx < arr->count
    • out_value non-NULL (address of a T variable)
  • Failure behavior: returns false when any precondition fails; does not write *out_value on failure.
  • Complexity: O(1).

array_try_set(arr, idx, value) -> bool

  • Purpose: checked element assignment by index.
  • Preconditions:
    • arr non-NULL
    • idx < arr->count
    • value can be assigned to the array element type.
  • Failure behavior: returns false when arr is null or idx is out of range.
  • Complexity: O(1).

array_try_slice_t(T, arr, low, high, out_slice) -> bool

  • Purpose: build checked owner-relative slice range for half-open range [low, high).
  • Preconditions:
    • arr non-NULL
    • low <= high <= arr->count
    • out_slice non-NULL
  • Failure behavior: returns false on invalid bounds or null pointers.
  • Complexity: O(1) (no element copy, no element pointer retained).
  • Notes: the resulting Slice(T) stores start and count, so it is safe to keep across array growth as long as the current array still contains that range.

array_try_slice_at_t(T, arr, slice, idx, out_ptr) -> bool

  • Purpose: checked pointer access through a Slice(T) range.
  • Preconditions:
    • arr non-NULL
    • idx < slice.count
    • slice.start + slice.count <= arr->count
    • out_ptr non-NULL
  • Failure behavior: returns false when any precondition fails.
  • Complexity: O(1).
  • Notes: the returned pointer is temporary and invalidated if the array is reallocated or freed.

array_try_span_t(T, arr, slice, out_span) -> bool

  • Purpose: materialize a temporary raw pointer Span(T) from a current array and a Slice(T) range.
  • Preconditions:
    • arr non-NULL
    • slice.start + slice.count <= arr->count
    • out_span non-NULL
  • Failure behavior: returns false when the slice is no longer valid for the current array.
  • Complexity: O(1).
  • Notes: the returned span is a borrowed pointer view and becomes dangling after backing storage is reallocated or freed.

slice_from_array_t(T, arr, low, high) (unchecked)

  • Purpose: build owner-relative slice range without validation.
  • Preconditions: arr non-NULL and bounds valid.
  • Failure behavior: undefined behavior if preconditions are violated.
  • Complexity: O(1).

span_make_t(T, elements, count)

  • Purpose: build an explicit temporary raw pointer span over arbitrary contiguous memory.
  • Preconditions: elements points to at least count elements, unless count == 0.
  • Failure behavior: undefined behavior if callers later use an invalid backing pointer.
  • Complexity: O(1).

array_back_ptr(arr)

  • Purpose: safe pointer to last element.
  • Preconditions: none.
  • Failure behavior: returns NULL when arr is NULL or empty.
  • Complexity: O(1).

array_start(arr)

  • Purpose: pointer to first element slot.
  • Preconditions: arr non-NULL.
  • Failure behavior: undefined behavior if arr is NULL.
  • Complexity: O(1).

array_end(arr) / array_end_unchecked(arr) (unchecked)

  • Purpose: pointer to last element.
  • Preconditions: arr non-NULL and non-empty.
  • Failure behavior: undefined behavior if preconditions are violated.
  • Complexity: O(1).

Iteration

array_for_each_t(T, arr, it)

  • Purpose: strict C typed pointer iteration from start to end.
  • Preconditions: arr non-NULL.
  • Failure behavior: undefined behavior if arr is NULL.
  • Complexity: O(n) over count.

array_for_each(arr, it) (GNU/Clang convenience)

  • Purpose: inferred-type iteration where typeof is available.
  • Preconditions: ARRAY_HAS_TYPEOF is 1 (GNU/Clang, not strict ISO C) and arr non-NULL.
  • Failure behavior: macro is unavailable when ARRAY_HAS_TYPEOF is 0; otherwise same preconditions as typed iteration.
  • Complexity: O(n).

slice_from_array(arr, low, high) (GNU/Clang convenience)

  • Purpose: unchecked owner-relative slice range creation with inferred element type.
  • Preconditions: ARRAY_HAS_TYPEOF is 1; bounds valid.
  • Failure behavior: unavailable in strict mode; undefined behavior if bounds are invalid.
  • Complexity: O(1).

span_make(elements, count) (GNU/Clang convenience)

  • Purpose: unchecked raw span creation with inferred element type.
  • Preconditions: ARRAY_HAS_TYPEOF is 1; backing pointer remains valid while the span is used.
  • Failure behavior: unavailable in strict mode; undefined behavior if callers later use an invalid backing pointer.
  • Complexity: O(1).

Metadata and nullable helpers

array_length(arr) / array_is_empty(arr)

  • Purpose: read logical size and emptiness.
  • Preconditions: arr non-NULL.
  • Failure behavior: undefined behavior if arr is NULL.
  • Complexity: O(1).

array_length_or0(arr) / array_is_empty_or_true(arr)

  • Purpose: nullable-safe metadata helpers.
  • Preconditions: none.
  • Failure behavior: none (NULL maps to 0 / true).
  • Complexity: O(1).

Checked vs unchecked guidance

  • Default for new code: checked APIs (array_try_*, array_reserve, nullable helpers).
  • Use unchecked compatibility APIs only when preconditions are guaranteed locally and reviewed.

Common Pitfalls & Best Practices

The library is defensively written, but certain patterns consistently cause problems. These are not bugs in the implementation — they are sharp edges inherent to C macros and pointer semantics.

1. Prefer array_try_pusharray_push discards failures

array_push(arr, value);           // Silently does nothing on allocation failure

if (!array_try_push(arr, value))  // Preferred
    goto fail;

2. Do not keep raw pointers or Spans across growth

Any of these can invalidate previously obtained pointers:

  • array_reserve
  • array_try_push / array_push
  • array_free

Bad pattern:

int *p = NULL;
array_try_at(values, 0, &p);

array_try_push(values, 42);   // May realloc and move the array

printf("%d\n", *p);           // Undefined behavior

Correct pattern:

int *p = NULL;
if (array_try_at(values, 0, &p)) {
    printf("%d\n", *p);
}

if (!array_try_push(values, 42)) goto fail;

// Re-acquire the pointer after any growth
if (array_try_at(values, 0, &p)) {
    printf("%d\n", *p);
}

Slice(T) objects are safe across growth (they store indexes). Span(T) objects are not.

3. Slice(T) is stable across reallocations — Span(T) is not

  • A Slice stores start + count. It remains valid as long as the range still exists in the current array.
  • A Span (and any pointer from array_try_at or array_back_ptr) stores a raw pointer and becomes dangling on realloc.

Always re-materialize Spans after growth:

Span(int) view;
if (array_try_span_t(int, arr, my_slice, &view)) { ... }

4. Prefer array_back_ptr over array_end

array_end(arr) returns a pointer to the last element and is undefined behavior on empty arrays. The name is misleading to most C programmers (who expect "end" to mean one-past-the-end).

Use the safe, nullable version instead:

int *last = array_back_ptr(arr);   // NULL when empty or NULL array
if (last) printf("%d\n", *last);

array_end and array_end_unchecked are compatibility names for the unchecked path.

5. Use typed NULL with checked/inferred macros

Passing a bare NULL literal to some checked macros can cause compile errors in strict -std=c11 -pedantic mode because of how the macros are written.

Recommended patterns (in order of preference):

// Best with the helper (new in this library)
array_try_get(array_null(int), 0, &v);

// Good
Array(int) missing = NULL;
array_try_get(missing, 0, &v);

// Also works
array_try_get( (Array(int))0 , 0, &v);

6. array_reserve and array_try_push can change your variable

These macros require a modifiable lvalue because growth may call realloc and update the pointer:

// BAD — the update happens to a local copy
Array(int) *p = &my_array;
array_reserve(*p, 100);

// GOOD
array_reserve(my_array, 100);   // my_array itself may be reassigned

Passing through a pointer is a common source of bugs:

void grow(Array(int) *p) {
    array_reserve(*p, 100);   // Updates the local *p, not caller's variable
}

Never store Array(T) behind an extra level of indirection if you intend to grow it. The library is designed around direct lvalue use.

7. Growth policy may surprise you

array_make(int, 5) followed by array_reserve(arr, 6) results in capacity 10, not 8. The policy doubles from the current capacity, not the next power of two. This is intentional but different from many other dynamic array libraries.

8. Enable ARRAY_DEBUG during development

Define ARRAY_DEBUG (e.g. -DARRAY_DEBUG) before including the header to turn on extra runtime assertions. These catch invariant violations (such as count > capacity or invalid slices) early.

This is highly recommended while you are still learning the ownership and invalidation rules. The checks have zero cost when ARRAY_DEBUG is not defined.

Internal guarantees used by public macros

  • Zero-capacity arrays grow correctly on first push.
  • Size arithmetic is overflow-checked before allocation.
  • Failed growth leaves existing array data intact.
  • Checked slice creation validates half-open bounds and output pointer.
  • Slice(T) ranges survive array reallocation; Span(T) and pointers do not.