Recent

Author Topic: Why does FPC behave this way? Maybe we should create an issue?  (Read 1941 times)

Thaddy

  • Hero Member
  • *****
  • Posts: 18729
  • To Europe: simply sell USA bonds: dollar collapses
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #15 on: January 12, 2026, 12:54:24 pm »
In C taking the difference of two pointers IS allowed.
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12634
  • FPC developer.
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #16 on: January 12, 2026, 01:08:44 pm »
Such subtractions have been long used to get fields offsets (@pa.field-@pa.firstfield). Older delphis required integer casts for that, and a lot of "integer" was used, which caused problems with 64-bit.

So newer versions of Delphi and FPC allow more with pointers to avoid integer typecasts. Incrementing pointers with pointers however has no meaning.

Khrys

  • Sr. Member
  • ****
  • Posts: 390
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #17 on: January 12, 2026, 01:19:04 pm »
[...] it makes no practical sense to subtract nothingness from nothingness.

This got me thinking:

Code: Pascal  [Select][+][-]
  1. type
  2.   TEmpty = record end;
  3.   PEmpty = ^TEmpty;
  4.  
  5. function PtrDiff(A, B: PEmpty): PtrInt;
  6. begin
  7.   Result := B - A; // No warnings
  8. end;

Code: ASM  [Select][+][-]
  1. mov rax, rsi
  2. sub rax, rdi
  3. ret

GCC says  error: arithmetic on pointer to an empty aggregate,  while Clang just warns that  subtraction of pointers to type 'struct Empty' of zero size has undefined behavior.
How does Delphi handle this?

BrunoK

  • Hero Member
  • *****
  • Posts: 765
  • Retired programmer
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #18 on: January 12, 2026, 02:45:54 pm »

This got me thinking:

Code: Pascal  [Select][+][-]
  1. type
  2.   TEmpty = record end;
  3.   PEmpty = ^TEmpty;
  4.  
  5. function PtrDiff(A, B: PEmpty): PtrInt;
  6. begin
  7.   Result := B - A; // No warnings
  8. end;

Code: ASM  [Select][+][-]
  1. mov rax, rsi
  2. sub rax, rdi
  3. ret

GCC says  error: arithmetic on pointer to an empty aggregate,  while Clang just warns that  subtraction of pointers to type 'struct Empty' of zero size has undefined behavior.
How does Delphi handle this?
Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3.  
  4. type
  5.   TEmpty = record
  6.   end;
  7.   PEmpty = ^TEmpty;
  8.  
  9.   function PtrDiff(A, B: PEmpty): PtrInt;
  10.   begin
  11.     Result := B - A; // No warnings
  12.   end;
  13.  
  14.   procedure TestPtrDiff;
  15.   var
  16.     pe1, pe2: PEmpty;
  17.   begin
  18.     WriteLn(PtrDiff(pe1, pe2));
  19.   end;
  20.  
  21. var
  22.   a, b: integer;
  23.   vBool: boolean;
  24.   e1, e2: TEmpty;
  25.   pe1, pe2: PEmpty;
  26. begin
  27.   vBool := (Pointer(pbyte(A) - pbyte(B)) = Pointer(PInteger(A) - PInteger(B)));
  28.   WriteLn(vBool, LineEnding);
  29.  
  30.   WriteLn(PtrDiff(pe1, pe2),LineEnding);
  31.  
  32.   pe1 := @e1;
  33.   pe2 := @e2;
  34.   WriteLn(PtrDiff(pe1, pe2),LineEnding);
  35.   TestPtrDiff;
  36.   ReadLn;
  37. end.

Compiles with messages and warnings :
Quote
project1.lpr(18,29) Warning: Local variable "pe2" does not seem to be initialized
project1.lpr(18,24) Warning: Local variable "pe1" does not seem to be initialized
project1.lpr(27,21) Warning: Conversion between ordinals and pointers is not portable
project1.lpr(27,32) Warning: Conversion between ordinals and pointers is not portable
project1.lpr(27,27) Warning: Variable "a" does not seem to be initialized
project1.lpr(27,38) Warning: Variable "b" does not seem to be initialized
project1.lpr(27,13) Hint: Conversion between ordinals and pointers is not portable
project1.lpr(27,52) Warning: Conversion between ordinals and pointers is not portable
project1.lpr(27,66) Warning: Conversion between ordinals and pointers is not portable
project1.lpr(27,44) Hint: Conversion between ordinals and pointers is not portable
project1.lpr(30,27) Warning: Variable "pe2" does not seem to be initialized
project1.lpr(30,22) Warning: Variable "pe1" does not seem to be initialized

What is there more to ask ?

Such subtractions have been long used to get fields offsets (@pa.field-@pa.firstfield). Older delphis required integer casts for that, and a lot of "integer" was used, which caused problems with 64-bit.
That's all there is to it, as marcov comments rightly.

Using calculated offsets to record that would be made of variant records in unions can use the mechanism to write / append consistent union-ed sub records.

If you don't need that mechanism, the don't use it ...


marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12634
  • FPC developer.
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #19 on: January 12, 2026, 04:49:10 pm »
GCC says  error: arithmetic on pointer to an empty aggregate,  while Clang just warns that  subtraction of pointers to type 'struct Empty' of zero size has undefined behavior.
How does Delphi handle this?

It depends on how you define the subtraction. Number of elements or number of bytes (as used for offsets).

In FPC it depends on $T. With $T-, Subtraction of typed pointers still return a byte offset in FPC. When $T+ it returns the number of elements.

Both are 0 if the record type is empty, no warnings.

My older Delphi (Seattle, 10.0) doesn't compile it, complaining about "operator not applicable to this operand type".

« Last Edit: January 12, 2026, 04:52:13 pm by marcov »

VisualLab

  • Hero Member
  • *****
  • Posts: 711
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #20 on: January 12, 2026, 05:30:41 pm »
Note that in C, a void* is _not_ semantically identical to a Pascal untyped pointer because in C, there is the concept of a void item/object.  That does not exist in Pascal, i.e, there is no ^; (pointer to semicolon, which would be the Pascal "pointer to empty", overloading the semicolon to mean not only empty statement but "empty anything" (which is C's void))  IOW, by definition, in C a void* doesn't point to a byte instead it points into nothingness and it makes no practical sense to subtract nothingness from nothingness.  If C allowed the subtraction of two void* then the only possible result would be zero reqardless of their value because they both point to the same thing: nothing!

Is this really the case? Various online tutorials claim that:
  • void informs the compiler that nothing will appear at a given location, i.e.:

    • if void is specified in parentheses after the function name, it means that the function doesn't actually take any arguments,
    • if void is specified on the left side of the function name (separated by a space), it means that the function doesn't actually return any result,

  • void* is a pointer to something (amorphous, amorphous), but it's not nothingness like the first case; it's a pointer without type information, and that's it.

So void (without *) and void* (with *) are two different things. The first is information about the absence of anything, the second is a pointer to something. So "void*" is basically the same as Pointer in Pascal. The mess is due to the idiotic syntax of the C language, invented by Denis R. This is prone to code errors.



Code: Pascal  [Select][+][-]
  1. program Project1;
  2.  
  3. type
  4.   TEmpty = record end;
  5.   PEmpty = ^TEmpty;
  6.   PNothing = Pointer;
  7.  
  8. function PtrDiffEmpty(A, B: PEmpty): PtrInt;
  9. begin
  10.   Result := B - A; // No warnings
  11. end;
  12.  
  13. function PtrDiffNothing(A, B: PNothing): PtrInt;
  14. begin
  15.   Result := B - A; // No warnings
  16. end;
  17.  
  18. var
  19.   X, Y: PEmpty;
  20.   K, L: PNothing;
  21.  
  22. begin
  23.  // New(X);
  24.  // New(Y);
  25.   Writeln('Result "Empty": ', PtrDiffEmpty(X, Y));
  26.   Writeln('Result "Nothing": ', PtrDiffNothing(K, L));
  27.   Readln;
  28. end.

The compiler reports that:

Quote
project1.lpr(25,48) Warning: Variable "Y" does not seem to be initialized
project1.lpr(25,45) Warning: Variable "X" does not seem to be initialized
project1.lpr(26,52) Warning: Variable "L" does not seem to be initialized
project1.lpr(26,49) Warning: Variable "K" does not seem to be initialized

But the code compiles. The results are shown in the attachment.
« Last Edit: January 12, 2026, 05:32:24 pm by VisualLab »

Thaddy

  • Hero Member
  • *****
  • Posts: 18729
  • To Europe: simply sell USA bonds: dollar collapses
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #21 on: January 12, 2026, 05:58:13 pm »
How does Delphi handle this?
Delphi 7 and Delphi 12.x do not allow it at all and does not warn but errors.
That is unless you apply a cast festival. (Same as for C, except C allows pointer difference, even if with a warning in modern C compilers)
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

440bx

  • Hero Member
  • *****
  • Posts: 6069
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #22 on: January 12, 2026, 06:12:15 pm »
So void (without *) and void* (with *) are two different things. The first is information about the absence of anything, the second is a pointer to something. So "void*" is basically the same as Pointer in Pascal. The mess is due to the idiotic syntax of the C language, invented by Denis R. This is prone to code errors.
There is a problem in that interpretation.

You got the first part right, void is the absence of anything.  Creating a pointer to void doesn't change what void itself is.  void* is a pointer to the "absence of anything" which is a pointer to nothing.  What you stated in the second part is akin to saying that an integer is not the target of a pointer to integer.  The fact that it is a pointer to integer doesn't change the integer in any way.  Same thing with void, void is void whether by itself or the target of a pointer.

void is defined in the C standard.  There is no equivalent in Pascal, i.e, void is something that does not exist in Pascal.  In Pascal there is no such thing as "nothing"/"void".  Just in case, for the record and without pun intended, an empty record is _not_ what void is in C.

That's the reason C compilers complain about some operations on void*.  A void* in C is _not_ a pointer to a byte.  Whereas in Pascal a "pointer" is implicitly a pointer to a byte, which is why it is sensible to subtract two plain/untyped pointers in Pascal, while in C, it is a dubious operation due to the definition of "void".

I don't know what Delphi does but FPC has it right.  You can increment or decrement an untyped pointer and, that's as it should be.  OTH, that said, it would be clearer to cast the pointer to pbyte.  That would make the programmer's intention clear instead of relying on compiler behavior that may not be obvious.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

VisualLab

  • Hero Member
  • *****
  • Posts: 711
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #23 on: January 12, 2026, 07:31:04 pm »
There is a problem in that interpretation.

Yes.

void is defined in the C standard.  There is no equivalent in Pascal, i.e, void is something that does not exist in Pascal.  In Pascal there is no such thing as "nothing"/"void".  Just in case, for the record and without pun intended, an empty record is _not_ what void is in C.

Yes, and very good, because it was Denis's idiotic idea. Wirth rightly assumed that if something does not exist, there is no need to write it down. Thanks to this approach, the code is understandable and clearer.

That's the reason C compilers complain about some operations on void*.  A void* in C is _not_ a pointer to a byte.  Whereas in Pascal a "pointer" is implicitly a pointer to a byte, which is why it is sensible to subtract two plain/untyped pointers in Pascal, while in C, it is a dubious operation due to the definition of "void".

Yes, but void* is not a pointer to nothing (emptiness). So what is a pointer that stores nullptr (in Pascal: nil)?

void* stores a real address (when it does not store nullptr), since it can be cast to a typed pointer. It is only unknown what object is located at this address. But it's not void. Otherwise, the cast wouldn't be possible (and it wouldn't make sense). It's simply a stupid way to represent the fact that it's a pointer to anything. Stupid because Denis used a previously defined word but with a slight modification (asterisk). And it caused confusion. People are reading "God knows what" into it. And this is just an idiotic notation.

Now, after all these years, it's hard to figure out what Denis really meant when he came up with "void*." Perhaps after many years Denis no longer wanted to comment on the genesis of his idiotic ideas. After all, he was a renowned computer scientist, so it's understandable that he didn't want to lose his "crown" :)

In any case, C has many such bizarre ideas, the origins of which have become mythologized over the years. Both the textbooks and the C language descriptions now resemble the Talmud (Catechism, or other interpretations): a collection of commentaries on commentaries, which in turn were commentaries on even older commentaries, which were on even older ones, and somewhere at the end, after many layers, were the original references to the Torah. Something that had been put together over years. Such a "multi-layered manipulation", where the original meaning has been completely blurred (or maybe even lost).

The only thing that is widely repeated nowadays is the myth that: "the creator of C was an exceptionally brilliant computer scientist and created an IT miracle." And if someone tries to analyze this from a technical perspective and, "God forbid," tries to point out flaws in the C language's design, they are considered an IT blasphemer. Because such time-honored dogmas cannot be questioned. Fortunately, this forum is rarely visited by "IT inquisitors" :)

I don't know what Delphi does but FPC has it right.  You can increment or decrement an untyped pointer and, that's as it should be.  OTH, that said, it would be clearer to cast the pointer to pbyte.  That would make the programmer's intention clear instead of relying on compiler behavior that may not be obvious.

Yes.
« Last Edit: January 12, 2026, 07:33:46 pm by VisualLab »

PascalDragon

  • Hero Member
  • *****
  • Posts: 6311
  • Compiler Developer
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #24 on: January 12, 2026, 10:01:18 pm »
How does Delphi handle this?
Delphi 7 and Delphi 12.x do not allow it at all and does not warn but errors.
That is unless you apply a cast festival. (Same as for C, except C allows pointer difference, even if with a warning in modern C compilers)

Or enable $PointerMath:

Code: Pascal  [Select][+][-]
  1. program tptrdiff;
  2.  
  3. {$APPTYPE CONSOLE}
  4. {$POINTERMATH ON}
  5.  
  6. type
  7.   TEmpty = record
  8.   end;
  9.   PEmpty = ^TEmpty;
  10.  
  11. var
  12.   p1: PEmpty = nil;
  13.   p2: PEmpty = nil;
  14.   r: NativeInt;
  15. begin
  16.   r := p2 - p1; // OK
  17.   //r := p2 + p1; // Error
  18. end.

ALLIGATOR

  • Sr. Member
  • ****
  • Posts: 370
  • I use FPC [main] 💪🐯💪
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #25 on: January 13, 2026, 04:22:47 am »
Code: Pascal  [Select][+][-]
  1. program app;
  2.  
  3. var
  4.   p1: Pointer;
  5.   p2: Pointer;
  6.   r:  Pointer;
  7.  
  8. function AddPointers(l, r: Pointer): Pointer; inline;
  9. var
  10.   null: Pointer = nil;
  11. begin
  12.   result := l - (null - r);
  13. end;
  14.  
  15. begin
  16.   r := AddPointers(p1, p2);
  17. end.

I wonder how likely it is that this is a shot in the foot?  :D
I may seem rude - please don't take it personally

440bx

  • Hero Member
  • *****
  • Posts: 6069
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #26 on: January 13, 2026, 04:41:50 am »
I wonder how likely it is that this is a shot in the foot?  :D
What you got there is a roundabout way of adding two pointers.

You can achieve the same result by casting the second pointer (r in your example) to ptruint.  if you do that, the compiler will interpret the pointer as a distance from "l" and do the calculation.  Basically, you were successful in adding two pointers but, the result is rather meaningless, therefore utterly useless.

Another way of doing it would be to "absolute" a ptruint var on top of "r".  That would allow you to directly add the absoluted identifier to "l".

There is more than one way to skin a cat, there is also more than one way for a programmer to shoot (him/her)self in the foot.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Thaddy

  • Hero Member
  • *****
  • Posts: 18729
  • To Europe: simply sell USA bonds: dollar collapses
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #27 on: January 13, 2026, 10:39:31 am »
Or enable $PointerMath:
Code: Pascal  [Select][+][-]
  1. type
  2.   TEmpty = record
  3.   end;
  4.   PEmpty = ^TEmpty;
  5.  
We have that already in trunk and was introduced on my request/patch:
https://www.freepascal.org/docs-html/rtl/system/topaquedata.html
https://www.freepascal.org/docs-html/rtl/system/popaquedata.html
https://www.freepascal.org/docs-html/rtl/system/opaquepointer.html

But it is a bit cheating away from untyped pointers... Casting in disguise.
(Although this was originally intended to pass C structures around)
« Last Edit: January 13, 2026, 10:46:03 am by Thaddy »
If Europe sells their USA bonds the USD will collapse. Europe can affort that given average state debts. The USA can't affort that. Just an advice...

PascalDragon

  • Hero Member
  • *****
  • Posts: 6311
  • Compiler Developer
Re: Why does FPC behave this way? Maybe we should create an issue?
« Reply #28 on: January 15, 2026, 09:26:24 pm »
Or enable $PointerMath:
Code: Pascal  [Select][+][-]
  1. type
  2.   TEmpty = record
  3.   end;
  4.   PEmpty = ^TEmpty;
  5.  
We have that already in trunk and was introduced on my request/patch:
https://www.freepascal.org/docs-html/rtl/system/topaquedata.html
https://www.freepascal.org/docs-html/rtl/system/popaquedata.html
https://www.freepascal.org/docs-html/rtl/system/opaquepointer.html

That's not the point! >:( The point is that a current Delphi does compile a difference of pointers to an empty record if $PointerMath is enabled!

 

TinyPortal © 2005-2018