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:

  1. AllocDosObject() — allocates properly-aligned DOS structures:
struct FileInfoBlock *fib = AllocDosObject(DOS_FIB, NULL);
/* ... use fib ... */
FreeDosObject(DOS_FIB, fib);
  1. AllocMem() / AllocVec() — exec memory is always longword-aligned.

  2. The D_S macro — 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

  1. Never dereference a BPTR directly — pass it only to dos.library functions, or convert with BADDR().
  2. File handles and locks are BPTRs — treat them as opaque values. Open() returns a BPTR, not a FILE *.
  3. Use D_S() or AllocDosObject() for any DOS structure that lives on the stack.
  4. 255 chars max for any string going through dos.library.
  5. ZERO (not NULL) 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