Memory ownership and in-place operations

Read side by side

On this page

A ppf_t owns or refers to significand storage; a context owns mutable execution policy and reusable scratch space. Treat both as managed C objects with explicit lifetimes. The most reliable application structure initializes them once, checks each fallible call, and releases them through one cleanup path. That structure is also the foundation for safe in-place operations and for recovery from allocation failure.

Establish ownership before computing

For local numbers, zero-initialize the structure before a sequence of initialization calls. This allows a common cleanup path to call ppf_clear on objects whose initialization was never attempted. Successfully initialize the context before using it to initialize numbers. A failure to allocate a significand during valid ppf_init2_ctx initialization leaves a safe-zero object; it does not leave a usable numerical value.

ppf_context_t ctx;
ppf_t x = {0}, y = {0};

if (ppf_context_init(&ctx) != 0) return 1;
if (ppf_init2_ctx(&x, 128, &ctx) != 0 ||
    ppf_init2_ctx(&y, 128, &ctx) != 0) {
    ppf_clear(&y);
    ppf_clear(&x);
    ppf_context_clear(&ctx);
    return 1;
}
/* Assign values and perform checked calculations here. */
ppf_clear(&y);
ppf_clear(&x);
ppf_context_clear(&ctx);

Do not initialize an already live object again to change its precision. That can abandon its existing allocation. ppf_set_prec changes precision but discards the old value on success, leaving NaN. Use ppf_prec_round when retaining and rounding the old value is the intention. ppf_reserve reserves capacity; it is not itself a request to change the numerical precision or value.

Do not copy a live ppf_t with C structure assignment or memcpy. That copies the storage pointer and ownership metadata, so two apparent objects can refer to one allocation and later free it twice. Use ppf_set to assign a value into an initialized destination, and ppf_swap when ownership of two complete objects should be exchanged. Similarly, use ppf_context_clone to create a context with copied policy and its own initially empty scratch state; a raw copy can duplicate ownership of arena blocks.

Supplying an allocator

ppf_context_init_custom accepts a ppf_allocator_t containing allocation, reallocation, and release callbacks plus an opaque pointer. Supply all three callbacks. The reallocation callback receives old and new byte sizes; it must leave the old block valid when it returns null. The release callback receives the size associated with that allocation. Respect these sizes when integrating a pool, accounting system, or debugging allocator.

Objects retain the allocator needed to release their own significands. An allocated ppf_get_str result likewise retains allocator provenance. This does not extend the lifetime of the opaque application state. Keep that state, and any supporting pool or callback code, alive until every dependent number, string, arena, and other owned resource has been released. Clearing the context is not a substitute for clearing the numbers created through it.

The complete systems_allocation.c example uses a small allocator wrapper that can reject its next request. It first stores 37, forces the temporary-object allocation in ppf_set_str to fail, and checks that 37 remains stored. After clearing the allocation flag, a retry reads 1.25 successfully. The program also checks that all blocks allocated through the wrapper have been released. This is an example of the project allocator contract, not a simulation of every possible system-wide out-of-memory condition.

If several contexts share one allocator and opaque pointer, the allocator's own synchronization must support the way those contexts are used. Separate contexts do not make a shared allocation counter or pool thread-safe.

What failure preserves

The library distinguishes initialization, assignment, and temporary resource construction. Their failure outcomes are useful only when the destination was valid and the function's preconditions were met.

Operation class Useful failure rule
Significand allocation during ppf_init2_ctx The object is safe to clear; there is no previous value to preserve
Covered numerical transaction with an initialized destination An unrecovered project-allocation failure preserves that destination
ppf_reserve or ppf_set_prec reallocation Failure preserves the original object and allocation
Initializer followed by assignment A later setter failure may leave an initialized object that still needs clearing
Stream input The number can be preserved although input bytes were consumed
Stream output Staging can fail before writing; an I/O failure after writing starts can leave a prefix

PPF_FLAG_ALLOC is sticky. It says that a project-controlled allocation was refused; it does not by itself say that the operation failed. Some operations recover through an exact fallback and return a normal result with the flag still set. Check the function's result first, then interpret the flag according to the application's resource policy. For an isolated measurement, clear the flag on the same context before the operation. The short convenience flag functions inspect the thread-default context, which may be different from an explicit context passed to the calculation.

The transactional rule covers the specified project-allocation paths. It is not a general promise to preserve the old value for every numerical error. Domain errors, unresolved numerical certification, invalid arguments, and ordinary overflow have their own documented outcomes. Nor does it promise rollback of all externally visible state: flags can change, input streams can advance, and formatting %n conversions can already have written a count.

GMP allocations form a separate domain from ppf_context_t. A custom Polyphase allocator does not intercept every temporary integer or rational allocated by GMP. C-library stream buffering, some process allocations, and operating-system resources are separate too. Plan application memory limits with those domains in mind. Do not replace GMP's process-level allocation functions casually inside an application that already has live GMP objects or other GMP users.

In-place arithmetic without accidental overlap

An in-place call such as ppf_add(&x, &x, &y, ...) means that one initialized object appears in an allowed destination and input position. It does not mean that unrelated objects may share arbitrary portions of their limb arrays. Consult the function's aliasing classification when building a generic wrapper or using an unfamiliar operation; similarity between function names is not a storage contract.

Ordinary arithmetic families support their documented in-place cases. The destination's precision still controls the rounded result, even when the destination is also an input. If different precision is needed for the result, create or prepare that destination explicitly rather than assuming an input's precision will be inherited.

Multi-output operations need special attention. The two floating outputs of ppf_modf, ppf_sin_cos, and ppf_sinh_cosh must be distinct, although either may individually alias the input where the contract permits it. ppf_grandom also requires distinct floating outputs when both are present; its second output may instead be null. ppf_get_constant_interval requires distinct lower and upper GMP integer objects. Two pointer variables pointing to the same object are not distinct outputs.

For ppf_div_with_plan_many, a destination may coincide with its numerator at the same index. Duplicate destinations and cross-index destination/input overlap are rejected before the batch begins. This prevents a result written early in the batch from silently replacing an operand needed later. Check array relationships before submitting a batch, especially when arrays are assembled from slices or views.

Lifecycle functions are not ordinary in-place assignments. An initializer needs unused destination storage; initializing it from itself cannot preserve a value that initialization destroys. Direct-storage functions likewise have their own capacity, alignment, normalization, and overlap requirements.

Scratch space and caller-owned limbs

The context arena is useful when an integration needs temporary storage with a shared scope. Take a mark before allocations and release back to it after the temporary work. Growing the arena uses linked blocks and does not move earlier returned addresses. A failed growth request leaves the earlier block chain and mark usable. Restoring a mark ends the lifetime of allocations made after that mark; no saved pointer into those allocations may be used afterward.

ppf_arena_reset keeps reusable storage rather than necessarily returning every byte to the operating system. ppf_context_clear releases the context's arena. Do not use an arena mark as a permanent bookmark across context cleanup, and do not mix marks from different contexts.

Advanced external-significand storage avoids ordinary ownership of limb memory. Its backing allocation must have enough capacity and remain valid while the object uses it. Clearing an externally backed object does not transfer ownership of that backing allocation to the library. Growing, relocating, or editing the buffer requires the appropriate direct-storage procedure; bypassing it can break normalization and expose readers to inconsistent data. Start with ordinary owned objects unless the integration has a measured need for direct storage and can enforce these lifetimes.

At shutdown, release returned strings and user-owned numbers, destroy plans and random states, clear explicit contexts, and then perform any desired thread-local cache cleanup. That order keeps allocator state alive until its dependents are gone and makes repeated computations easier to reason about.