Skip to content

FreePascal pitfalls

YadeWira edited this page Nov 7, 2026 · 3 revisions

FreePascal pitfalls

Rules learned while writing PJA, all measured and none obvious. The first two are in pas/pja.inc.

  • {$R+} does not check pointer accesses. p[10] with p: PByte reads out of range without any error. Validation works only on arrays.
  • With {$Q+}, for i := 0 to n - 1 with unsigned n and n = 0 raises EIntOverflow. Lengths and indices are signed, and loops use High().
  • On i386, Int64 is not ordinal: it cannot be a for variable, and the error only shows up when compiling for 32 bits. Counters in SizeInt, and byte positions in Int64.
  • for s in ['short', 'much_longer'] truncates the literals to the length of the first one, and with numbers it builds a set of bytes. Iterate over a declared constant array instead.
  • On Unix, TFileStream.Create(f, fmOpenRead) takes an exclusive flock. A second open of the same file, even in the same process, fails with EAGAIN. For reading: fmOpenRead or fmShareDenyNone.
  • FPC does not warn about an unset Result if some path does Exit(value). pjafs.Parent returned whatever was left in the register, and it showed up as an intermittent failure (1 run in ~10). pas/results.py stops the build.
  • Pascal is case-insensitive. A variable version shadowed the constant VERSION and no future version was rejected; a variable plain shadowed the function Plain. The compiler does not warn in either case (the second was caught only because the types did not match). pas/collisions.py stops the build.
  • On 64 bits, FPC multiplies two Cardinals in 64 bits. A hash meant to wrap at 32 bits kept its high bits until the product was wrapped in Cardinal().
  • A global written from several threads costs more than it seems. The cost accounting wrote one per coefficient from every band thread; the cache line bouncing between cores made each band three times slower.
  • In a DLL, FPC 3.2.2's heap can delete its own lock. It counts the threads using it, the one that loaded the DLL first, and deletes heap_lock when the count comes back to zero. 7-Zip's file manager loads codecs from a short-lived thread: once it ended, the next block freed by a thread other than its owner (every new thread frees one) entered a deleted critical section, and 7zFM crashed testing a PJA archive (up to 0.6.1). Since 0.6.2 the Windows libraries use the process heap (pas/pjaheap.pas, first in their uses).
  • TThread.WaitFor naps 100 ms at a time on Unix when called from the main thread. Short files on several threads were up to 7 times slower; pas/pjathread.pas joins threads through an event.
  • System.ProcessorCount is 1 on Unix in FPC 3.2.2: pas/pjacpu.pas counts the processors itself.
  • The signal handler's type is SigActionHandler. SigActionHandler_t exists on Linux only, and the macOS build failed on it (0.9.5).
  • A threadvar is per thread, also in a library. A plugin hook that counts in threadvars gives wrong totals if the work of one file is spread over threads: the zpaq-std plugin codes a file's JPEG 2000 codestreams one after the other.
  • Not FreePascal, but linked with it: no 64-bit division in the C on 32-bit x86. clang -m32 turns it into a call to libgcc's __divdi3, which FPC does not link; nm -u on the objects catches it (0.9.5).

Clone this wiki locally