BPTR, BSTR, and BCPL Legacy¶
This article documents the BCPL-era types that permeate dos.library — a topic that causes more confusion than any other in AmigaOS programming. Source: the 2024 ROM Kernel Reference Manual by Thomas Richter.
Why BPTRs exist¶
AmigaDOS descends from the Tripos operating system, originally written in BCPL — a typeless language that treats memory as an array of 32-bit cells indexed from zero. A "pointer" in BCPL is not a byte address but an index into this 32-bit array. To maintain backward compatibility, AmigaDOS (even in the V36+ C rewrite) preserves this convention through BPTRs (BCPL Pointers).
typedef long BPTR; /* dos/dos.h — an index, NOT a real pointer */
The conversion macros¶
#define BADDR(x) ((APTR)((ULONG)(x) << 2)) /* BPTR → real C pointer (multiply by 4) */
#define MKBADDR(x) (((LONG)(x) >> 2)) /* real C pointer → BPTR (divide by 4) */
A BPTR of value 5 means "byte address 20" (5 × 4). BPTRs are always longword-aligned (divisible by 4) because they index into a 32-bit array.
The alignment trap¶
This is the #1 gotcha: C stack variables are not guaranteed to be longword-aligned. If you declare a DOS structure on the stack and pass it to dos.library, it may crash or corrupt data.
Three safe approaches:
AllocDosObject()— allocates properly-aligned DOS structures:
struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
/* ... use fib ... */
FreeDosObject(DOS_FIB, fib);
-
AllocMem()/AllocVec()— exec memory is always longword-aligned. -
The
D_Smacro — align stack variables manually:
#define D_S(type,name) char a_##name[sizeof(type)+3]; \
type *name = (type *)((ULONG)(a_##name+3) & ~3UL)
D_S(struct FileInfoBlock, fib); /* fib is now aligned */
This allocates 3 extra bytes and rounds the pointer up to the next 4-byte boundary. It's used throughout the NDK example code.
BSTRs — BCPL strings¶
BCPL strings differ fundamentally from C strings:
| Property | C string | BCPL string (BSTR) |
|---|---|---|
| Termination | NUL byte (0) at end | Length byte at start |
| Max length | Unlimited | 255 characters |
| Format | char[] |
UBYTE[0]=length, UBYTE[1..n]=chars |
| Passed as | char * |
BPTR to the length word |
typedef long BSTR; /* BPTR to a BCPL string */
dos.library functions accept C strings and convert internally, so you rarely handle BSTRs directly. But the 255-character limit is a hard constraint: any string passed to dos.library (including file paths, arguments, volume names) cannot exceed 255 characters. For longer paths, you must split them into components using FilePart(), PathPart(), AddPart().
Three kinds of zero¶
| Name | Meaning | Value |
|---|---|---|
ZERO |
First BCPL memory index = invalid BPTR | 0L |
NULL |
C pointer to address 0 | 0 / ((void *)0) |
NUL |
ASCII character code 0 (C string terminator) | '\0' |
All three are numerically zero, but conceptually different. ZERO is an index, NULL is a pointer, NUL is a character. Mixing them usually works but is semantically wrong.
Practical rules¶
- Never dereference a BPTR directly — pass it only to
dos.libraryfunctions, or convert withBADDR(). - File handles and locks are BPTRs — treat them as opaque values.
Open()returns a BPTR, not aFILE *. - Use
D_S()orAllocDosObject()for any DOS structure that lives on the stack. - 255 chars max for any string going through
dos.library. ZERO(notNULL) is the "invalid" value for BPTR-typed fields, though numerically identical.
See Also¶
Sources: Thomas Richter, "ROM Kernel Reference Manual: AmigaDOS" (2024 edition), §2.4–2.5.
Raw: raw/rkm/rkm-amigados-extract.md; raw/rkm/rkm-dos-book.md
Updated: 2026-08-08