Table of Contents

Work with stored pointers

A pointer in a binary file or message is a number that refers to another byte position. It is not a C# reference and must never be treated as a process memory address.

Three positions are easy to confuse:

  1. The pointer field position is where the encoded pointer bytes are stored.
  2. The stored address is the unsigned number decoded from those bytes.
  3. The effective target position is where CStructSharp reads the pointed-to value. In relative mode, this is the configured origin plus the stored address.

The layout and read options tell CStructSharp how to connect them.

A native C pointer normally addresses a live object in a process. Saving its numeric representation does not save its target or make the address valid after a restart. The background chapter on memory addresses and stored data explains virtual memory, file-relative offsets, and why the pointer width belongs to the format rather than the current operating system.

Follow a simple pointer

This layout stores a one-byte pointer to a one-byte value:

struct root {
    uint8 *target;
};

The executable example sets pointerSize: 1 and reads bytes 01 2A:

private static void FollowPointer()
{
    var layout = new CStruct("struct root { uint8 *target; };", pointerSize: 1);
    using var stream = new MemoryStream([0x01, 0x2A]);
    StructValue root = layout.Parse(stream, "root");
    Pointer pointer = root.Get<Pointer>("target");
    Equal(1L, pointer.Address);
    True(pointer.IsDereferenced, "Pointer should be followed by default.");
    Equal((byte)0x2A, (byte)pointer.Value!);
}
offset       0            1
bytes       01           2A
field       target       pointed-to uint8
stored      address 1 ───────► value 0x2A

The returned Pointer (from CStructSharp.Values) reports Address = 1, IsDereferenced = true, and Value = 0x2A. The stream contains no object allocation or relocation information; it contains only the coordinate 1.

Pointer width is part of the data format. Set it to 1, 2, 4, or 8 when constructing CStruct; do not copy the bitness of the .NET process.

Null and unresolved pointers

A stored zero is always null. A nonzero pointer can be left unresolved by setting ReadOptions.DereferencePointers = false.

State IsNull IsDereferenced Value
Stored zero true false null
Nonzero, following disabled false false null
Nonzero, followed successfully false true Decoded target

This distinction lets inspection tools display an address without reading untrusted target data.

Absolute and relative coordinates

Absolute mode treats the stored number as the target's stream position. Relative mode adds ReadOptions.Origin. Use relative mode only when the format specification says offsets are measured from a known base, such as the beginning of a record.

Zero remains null in both modes; the origin is not added to it. Effective targets must be non-negative, fit the stream coordinate range, and stay within the supplied memory region or readable stream.

A stored address of 4 targets offset 4 in Absolute mode and offset 12 in Relative mode with Origin 8 0 4 8 12 bytes 04 00 2A 2A Absolute ptr * Relative ptr O * Absolute: target = address. Relative: target = Origin (O, here 8) + address. Address 0 stays null.

Paths and multiple levels

After a pointer field:

  • .address selects the pointer field's stored coordinate for reading or writing;
  • .value follows one declared pointer level.

For uint8 **target, root.target.value reaches the second pointer and root.target.value.value reaches the final byte. Each level consumes traversal budget.

When a path follows a pointer to a struct, reading one of that struct's pointer fields consumes another level. Selecting the field does not restart MaxPointerDepth.

Serialization writes pointer coordinates. It does not move target objects, allocate storage for them, or fix up addresses automatically. The application must already know the correct coordinate.

Limits and troubleshooting

Use ReadOptions to limit pointer depth, bytes per fixed target, total physical bytes read, arrays, strings, and nested structures. These limits protect against cycles and addresses designed to make the reader traverse excessive data.

When following fails, inspect:

  1. pointer width and byte order;
  2. absolute versus relative mode and the origin;
  3. the stored address and effective target position;
  4. whether the stream or supplied memory contains the target;
  5. the number of .value levels; and
  6. pointer depth, target-size, and total-read limits.

Read Pointers and addressing for multi-level, union, array, write, and failure rules.

For unsigned virtual addresses and mapped images, use the memory APIs described in Analyze mapped memory. Their StoredPointer values preserve all 64 bits and their .value paths follow targets explicitly in a caller-selected address space.