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:
- The pointer field position is where the encoded pointer bytes are stored.
- The stored address is the unsigned number decoded from those bytes.
- 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.
Paths and multiple levels
After a pointer field:
.addressselects the pointer field's stored coordinate for reading or writing;.valuefollows 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:
- pointer width and byte order;
- absolute versus relative mode and the origin;
- the stored address and effective target position;
- whether the stream or supplied memory contains the target;
- the number of
.valuelevels; and - 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.