Compiler Checks and Runtime Errors
Free Pascal's compiler directives let you trade performance for safety during development: {$R+} enables range checking on array/string indexing, {$Q+} enables overflow checking on integer arithmetic, and {$C+} (or {$ASSERTIONS ON}) enables Assert statements to actually execute rather than compile to no-ops. When one of these checks fails, the program halts with a specific numbered runtime error — 201 for range-check errors, 216 for a general protection fault often caused by dereferencing a nil or dangling pointer, and 106 for an invalid numeric format during type conversion — and the exact error number is the fastest way to narrow down what category of bug you're dealing with before even opening the debugger. Production release builds typically disable these checks with {$R-}{$Q-} for performance, so it's standard practice to keep a debug build configuration in Lazarus with all checks enabled.
Cricket analogy: Range checking ({$R+}) is like a third umpire automatically flagging when a fielder's throw goes beyond the boundary rope before anyone even reviews the footage — an early, specific signal (runtime error 201) rather than letting an invalid play stand unnoticed.
Using the Lazarus Debugger
Lazarus's integrated debugger (typically backed by GDB, or LLDB on macOS) lets you set breakpoints by clicking the gutter next to a line, then step through code with F7 (Step Into), F8 (Step Over), and Shift+F8 (Step Out), inspecting live variable values in the Watches and Local Variables windows as execution pauses. Conditional breakpoints — right-click a breakpoint and set a condition like i = 47 — are essential when a bug only manifests deep inside a loop, since stepping manually through thousands of iterations to reach iteration 47 would be impractical; you can also set a 'pass count' so a breakpoint only triggers on, say, the 10th time it is hit. For a segmentation fault (runtime error 216) with no useful call stack, enabling {$C+} and stack-trace-friendly compiler options (-gl for line info, -gw for DWARF debug info) ensures the debugger's Call Stack window shows meaningful unit/line information instead of raw memory addresses.
Cricket analogy: Setting a conditional breakpoint like i=47 is like a coach only reviewing footage from the exact over a specific bowling error occurred, rather than rewatching an entire day's play — targeted inspection instead of exhaustively stepping through every ball.
Common Bug Patterns and Assertions
A frequent Pascal bug pattern is accessing a class field before calling the constructor, or calling a method on a nil object reference after a failed Create or an earlier Free — enabling {$C+} and sprinkling Assert(Obj <> nil, 'Obj was not created') at suspicious call sites turns a silent nil-dereference crash into an immediate, informative failure at the exact point of misuse rather than several stack frames later. Off-by-one errors in loops are another classic, especially around High()/Low() versus hardcoded bounds, since Pascal arrays are not guaranteed to be zero-based — a for i := 0 to Length(Arr) - 1 loop over an array declared with a nonstandard lower bound can silently skip or overrun elements, which is exactly the class of bug that {$R+} range checking is designed to catch during development.
Cricket analogy: An Assert catching a nil object is like a fielding coach checking a player has actually put on protective gear before entering a drill, immediately flagging the omission rather than discovering it only after an injury occurs mid-session.
{$mode objfpc}{$H+}
{$C+} // enable assertions
{$R+} // enable range checking
program DebugDemo;
uses
SysUtils;
type
TWidget = class
Value: Integer;
end;
var
W: TWidget;
Arr: array of Integer;
i, Sum: Integer;
begin
W := nil;
Assert(W <> nil, 'W was not created before use'); // fails loudly, with message
SetLength(Arr, 5);
for i := 0 to 3 do
Arr[i] := i * 2;
Sum := 0;
for i := Low(Arr) to High(Arr) do // safer than hardcoded 0..4
Sum := Sum + Arr[i]; // Arr[4] is uninitialized -> caught if used meaningfully
WriteLn('Sum = ', Sum);
end.Free Pascal's SysUtils unit provides GetExceptionStack and Exception.StackTrace helpers, but for reliable stack traces you must compile with -gl (line info) and often link a symbol-lookup unit like LineInfo or heaptrc into the uses clause; heaptrc additionally reports memory leaks at program exit, listing exactly which GetMem/New call was never matched by a FreeMem/Dispose.
- Enable
{$R+},{$Q+}, and{$C+}in debug builds to catch range, overflow, and assertion failures early. - Numbered runtime errors (201 range check, 216 access violation, 106 invalid format) narrow down the bug category immediately.
- The Lazarus debugger (GDB/LLDB-backed) supports breakpoints, conditional breakpoints, and pass counts.
- F7/F8/Shift+F8 step Into/Over/Out of function calls while inspecting the Watches and Local Variables windows.
- Compile with
-gl/-gwfor meaningful call stacks instead of raw memory addresses on crashes. Assertstatements catch nil-reference and precondition bugs immediately rather than several stack frames later.- The
heaptrcunit reports unmatchedGetMem/Newallocations as memory leaks at program exit.
Practice what you learned
1. What does runtime error 216 typically indicate in a Free Pascal program?
2. What is the purpose of a conditional breakpoint in the Lazarus debugger?
3. Why is `for i := Low(Arr) to High(Arr)` generally safer than `for i := 0 to Length(Arr) - 1`?
4. What does the `heaptrc` unit report when included in a Free Pascal program?
5. What compiler directive enables `Assert` statements to actually execute instead of compiling to no-ops?
Was this page helpful?
You May Also Like
Object-Oriented Pascal
Learn how Pascal implements classes, inheritance, polymorphism, and constructors/destructors through the `class` type and virtual methods.
Building GUI Apps with Lazarus
Learn how to build cross-platform desktop applications in Free Pascal using the Lazarus IDE, the LCL component library, and event-driven programming.
Generics in Free Pascal
Understand how Free Pascal's generic types and routines let you write reusable, type-safe containers and algorithms with `generic` and `specialize`.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics