User:Martok/Portability Issues
DRAFT VERSION
Free Pascal, especially in its TP and Delphi modes, is generally compatible with code written for the "Borland compilers" (Borland pascal, Turbo Pascal, and Delphi, including recent versions by CodeGear and Embarcadero). Where there are syntactic differences, they are usually documented. However, there are some differences where the same source code will produce different behaviour. Sometimes they lie in undefined behaviour that is interpreted differently, sometimes they are required because Free Pascal targets non-Intel platforms, sometimes they are because Free Pascal tends to default to ISO Pascal compatibility, sometimes they just are. This page attempts to collect these non-obvious differences.
Also see: Porting Turbo Pascal to Free Pascal
Range Checks
FPC is defined with mandatory Range Checks. They can be disabled for performance, but the programm will not be strictly defined then.
Borland | FPC |
---|---|
Range Checking is purely optional. In fact, TP is defined as having no runtime range checks at all, except for Succ/Pred (and Inc/Dec by extension), which explicitly react to $R .
Code may do things the programmer has forgotten to take care of, but if an operation on ordinals is accepted by the compiler at all, its result fully defined. |
Range Checks are mandatory. Many operations on ordinals are undefined in the (default) state {$R-} . Some (such as typecasts) do not generate range checks, so they are technically always undefined.
This is consistent with ISO Pascal, where they are treated as "dynamic violations". |
Further Reading
- TP 7 Language Guide, Chapter 4
- TP 7 Language Guide, Chapter 21
Subrange Types
Subrange Types are interpreted more strictly, following the rules-as-written of ISO 7185. This causes several follow-up differences, such as in dead code elimination or range checking.
type Percentile = 0..99;
var I: Percentile;
{ Ex. 1}
I := 100;
{ Ex. 2}
I := 99;
Inc(I);
Borland | FPC |
---|---|
Ex.1 does not compile. Ex.2 compiles and (in state {$R-} ) causes the value 100 to be stored in I and program execution continues or (in state {$R+} ) causes a Range Check Error. If no RCE was raised, the program continues safely.
Since TP, if Range Checks are disabled and a value to be assigned to a Subrange variable is outside that Subrange Type's range, the operation is instead carried out in the Host Type, all the way up to the corresponding basic Storage Type. For example in Delphi, |
Ex.1 compiles, but raises a warning. Ex.2 compiles and (in state {$R+} ) causes a Range Check Error at runtime. In either example, the program is now in an undefined state.
|
{ Continued from above }
if I > 99 then
Writeln('This is not a Percentage');
Borland | FPC |
---|---|
Does exactly what is written.
Note: If the expression was |
Since in the strict type system, I can never be larger than 99, the statement is considered always-false and removed as dead code. It is therefore impossible to generate the equivalent of Range-Check code in user code: even modifying the expression to if Integer(I) > 99 then is not formally guaranteed to work, since as soon as I left the declared range, operations on it became undefined.
|
type MyEnum = (zero = 0, one = 1, five = 5);
var E: MyEnum;
{ Ex.1 }
E:= one;
inc(E);
{ Ex.2 }
E:= MyEnum(3);
{ Ex.3 }
E:= five;
inc(E);
Borland | FPC |
---|---|
The Storage Type is decided by the setting of the PackEnum directive. The Host Type is the Subrange 0..5 . The explanations above apply, so all examples compile, with Ex.3 raising a Range Check Error if they are enabled. In any case, the program is in a well-defined state.
Note: Ex.1 and 2 do not raise a Range Check. The ordinals 2 to 4 do not have assigned names, but are members of the Host Type. |
The Storage Type is decided by the setting of the PackEnum directive. The Host Type is the Subrange 0..5 . All examples compile, but put the program in an undefined state.
Note: the compiler assumes that only declared values (here: the specific ordinals 0,1,5) are contained in the variable. Any other value, including those in the "gap", may lead to undefined behaviour. |
Further Reading
- http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Simple_Types_%28Delphi%29#Subrange_Types
- https://bugs.freepascal.org/view.php?id=34140
- https://bugs.freepascal.org/view.php?id=30423
- https://bugs.freepascal.org/view.php?id=32079
- https://bugs.freepascal.org/view.php?id=16006
Floating Point Literals & Constants
Floating-Point literals in Borland compilers are always of the largest available real type (Real or Extended). FPC uses the smallest exactly possible type.
const
five = 5.0;
nine = 9.0;
fiveninths = five / nine;
Writeln(fiveninths:30:20);
Borland | FPC |
---|---|
All constants are of type Extended. Evaluated at extended precision, 0.55555555555555556000 is printed.
|
five and nine are representable exactly as Single, so they are of type Single. Because it involves only type Single, the type of fiveninths is also single. 0.55555558200000000000 is printed.
|
Further Reading
- http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Declared_Constants#True_Constants
- https://bugs.freepascal.org/view.php?id=25121
- https://bugs.freepascal.org/view.php?id=36415
- http://docwiki.embarcadero.com/RADStudio/Tokyo/en/About_Floating-Point_Arithmetic
Managed variable lifetimes
FPC often uses more temporary variables during evaluation of complex expressions. If these are of managed types, this affects object lifetime.
Assume a managed variable is freed no sooner than at the end of the enclosing block scope, notwithstanding any explicit nil
assignments or similar clearing statements. This is actually a good assumption in general, as there are corner cases where Delphi also does what FPC always does.
TODO: concrete examples.
Managed Variable Intialization
For DFA purposes, FPC treats managed variables as uninitialized, even in cases where intialization is guaranteed.
program Project2;
var
a: TMyArray;
begin
SetLength(a, 12);
end.
Borland | FPC |
---|---|
Global variables are always initialized to nil/0. Therefore, a is considered initialized before the SetLength call. | Global variables are always initialized to nil/0. Nevertheless, a warning 5092 "uninitialized managed variable" is printed. |
function fn: TMyArray;
begin
end;
begin
a:= fn;
end.
Borland | FPC |
---|---|
The Result Variable is unintialized. Random memory at [ebp+4] ) is returned. No warning is emitted.
|
The Result Variable is unintialized. The result register eax is left untouched. A warning 5033 "uninitialized result variable".
|
procedure Proc;
var
a: TMyArray;
begin
i:= Length(a);
end;
Borland | FPC |
---|---|
Local managed variables are always fully initialized, as if passed to Initialize() . Nevertheless, a warning "uninitialized variable" (note: the generic one, not specific to managed vars) is printed.
|
Local managed variables are always fully initialized, as if passed to Initialize() . Nevertheless, a warning 5092 "uninitialized managed variable" is printed.
|
procedure Proc;
var
a: TMyArray;
begin
SetLength(a, 12);
end;
Borland | FPC |
---|---|
As above, but no message is printed - SetLength is a compilerproc with special semantics (maps to DynArraySetLength ), and does not raise a warning.
|
As above. SetLength is a a compilerproc with no special semantics (maps to fpc_dynarray_setlength ), and therefore prints a warning 5092 "uninitialized managed variable" is printed.
|