-5

I'm working in an application with some semi-colon abnormalities in Delphi 10.3.3. I've been reading some semi-colons are optional, depending on the compiler, when not between statements. For many years I thought this was a bug in the Delphi compiler to allow it. I can see the app compiles if they are dropped prior to any block ending statement.

Does a semi-colon between [except] and [end] serve any purpose in the code, such as faster run time or throw some default exception, or some other purpose? Could this throw any random compile or runtime errors?

Does leaving a semi-colon off the last line before an end or except speed up runtime? Is one way, more error-prone than another or subject to random compile or runtime errors?

Here is code that can be dropped into any test project with a form having an edit box, a button and a memo box created with drag-n-drop default settings.

function TForm1.isThisUseful(this : string): boolean;
var
  i: Integer;
begin
  result := false;
  try
    if this = ''
    then
      i := 0
    else
      i := 1
  except
    ;
  end;
  result := (i > 0)
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  if isThisUseful(Edit1.Text)
  then
    Memo1.Text := 'Yes, it is useful'
  else
    Memo1.Text := 'This is a waste of time.'
end;
Joseph Poirier
  • 191
  • 1
  • 11
  • 4
    In Pascal, semicolons have always functioned as a statement *separator*, not a terminator as in C. The compiler should simply ignore any superfluous colons and they should have no effect on the generated code. Pascal coders tend to include semicolons where they are not necessary, such as at the end of an begin...end block which is at the end of a routine, in case later the block is moved or another statement is inserted after it. – MartynA Feb 18 '20 at 18:05
  • True. I'm running a very large application. When I add-in semi-colons before [end] and [except] and remove those extra ones, the application run appears to slow down as compared to the prior state, which was the primary reason I posted the question – Joseph Poirier Feb 18 '20 at 19:14
  • 4
    "When I add-in semi-colons ..." All you need do is to compare the code generated with and without them using View | Debug Windows | CPU. Have you done that and found any difference? – MartynA Feb 18 '20 at 19:27
  • The generated code should be identical; otherwise, there is a bug in the compiler (and I *highly* doubt that). Hence, the run-time behaviour should be *identical*. (And, as discussed in the comments to my answer, the compilation time difference is likely *extremely* small, likely not even measurable even with 1000 superfluous semicolons.) – Andreas Rejbrand Feb 18 '20 at 19:58
  • compile time is un-noticeable, to the naked eye the code runs the same. But I haven't hit the fabled semi-colon in the except handler in debug mode. I will force that in a test app to see what it does. But the runtime is where it feels slower after removing the extra semi-colon & adding in missing ones prior to [end] & [except]. any other thoughts? – Joseph Poirier Feb 18 '20 at 20:04
  • 4
    Don't rely on subjective impressions. Get yourself a decent profiler and see if you can **measure** any difference. – MartynA Feb 18 '20 at 20:11
  • 2
    Semicolons do not affect runtime performance. They affect the way the compiler parses the code. – Ken White Feb 18 '20 at 21:59

4 Answers4

4

tl;dr: No difference whatsoever.

try
  // do stuff
except
  ;
end;

This semicolon can be removed. It has absolutely no effect whatsoever. It does no good, and it does no harm (except, possibly, it makes the source code look strange, which you should avoid1).

begin
  DoThis;
  DoThat;
end;

Since in Delphi, semicolons separate statements, the final semicolon before end is not necessary. It has absolutely no effect whatsoever. It is mainly a matter of taste if you want it or not (and personally, I sometimes have it and sometimes not).

One benefit of having this semicolon might be that you then can move or copy this line, without modification, to a different place where it isn't the final statement in its block. Similarly, you can also insert a new statement immediately after this line of code without needing to add the semicolon. IMHO, these are very minor benefits; it's not hard to add the semicolon if and when it becomes necessary.

Bonus comment: However, result := (i > 0) does look strange to me. I'd expect Result := i > 0, without the unnecessary parentheses. (This might be because I have programmed in Delphi almost daily for more than 20 years.)

Bonus comment 2: Notice that the compiler happily accepts

begin
  DoThis;;
  DoThat;
end;

as well. It simply ignores redundant semicolons. But the snippet above looks very ugly to me, and I always find it strange that developers choose to commit code without reading it first.

Bonus comment 3: Ken Bourassa wrote an answer giving an example of a scenario in which the presence or absense of a semicolon does affect the program. But there is an even scarier example:

case i of
  0:
    DoA;
  1:
    DoB;
  2:
    if j = 0 then
      DoC{;}
else
  DoD;
end;

1 In this case, you could also argue that the semicolon serves a purpose: it "highlights" the empty except block, making it slightly easier to spot quickly. Hence, it might be intentional. But one might argue that a better way to highlight it would be to include a comment like // Do nothing or // Just carry on. (And, in many, if not most, cases, empty except blocks are not good at all.)

Andreas Rejbrand
  • 95,177
  • 8
  • 253
  • 351
  • 2
    Well, of course, a superfluous semicolon increases the file size of the source code file, and increases the number of tokens in the source, so the time needed to compile it might increase a bit. But this effect is so minor that it isn't even measurable. Hence I felt no need to state that obvious fact in my answer. – Andreas Rejbrand Feb 18 '20 at 18:25
  • What if the number is in the 1000s? – Joseph Poirier Feb 18 '20 at 19:15
  • 1
    The tl;dr strikes me as rather a case of the pot and kettle ;=) – MartynA Feb 18 '20 at 19:29
  • 2
    @JosephPoirier: If you have 1000 superfluous semicolons in a file, it is likely a very large file. And the *percentage* of the file that these semicolons constitute is likely still very small. Hence, it still has no effect worthy of consideration. Yes, it might make the compilation take 12.5672 seconds instead of 12.5670 in average, but who cares? You could argue that you should stop using comments in code, or that you should only use single-letter variable and function names. That will make the file faster to read from disk. But it is obviously a *horrible* idea. – Andreas Rejbrand Feb 18 '20 at 19:35
  • 1
    (cont.) This (the effect of superfluous semicolons on compilation time) really is your least concern; don't think about it *at all*! There are 1000 other aspects of programming that are *far* more important! :) – Andreas Rejbrand Feb 18 '20 at 19:37
  • 4
    [Just to be clear, I don't think the effect of 1000 superfluous semicolons is even measurable.] – Andreas Rejbrand Feb 18 '20 at 19:43
  • @MartynA: Quite true. Regarding the actual question, there really isn't much to say beyond the tl;dr. – Andreas Rejbrand Feb 18 '20 at 23:50
2

I thought I would try some measurements, using the Line Timer of NexusDB's Quality Suite on this D7 test program:

{$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N+,O-,P+,Q-,R-,S-,T-,U-,V+,W+,X+,Y+,Z1}
program semicolontest;

{$APPTYPE CONSOLE}

uses
  SysUtils;

function isThisUseful1(this : string): boolean;
var
  i: Integer;
begin
  result := false;
  try
    if this = ''
    then
      i := 0
    else
      i := 1
  except
    ;
  end;
  result := (i > 0)
end;

function isThisUseful2(this : string): boolean;
var
  i: Integer;
begin
  result := false;
  try
    if this = ''
    then
      i := 0
    else
      i := 1
  except

  end;
  result := (i > 0)
end;

var
  Input : String[20];
  i : Integer;
  Res : Boolean;

begin

   Input := 'abcdefg';

   writeln('Starting');
   for i := 1 to 1000000 do begin
     Res := isThisUseful1(Input);
     Res := isThisUseful2(Input);
   end;
   writeln('Done');
   // readln;
end.

which contains two versions of the isThisUseful function from the question, one with, and one without the semicolon in the except block. The results of running this code (with apologies for the slightly mangled layout) are as follows:

Line    Total Time  Hit Count   Source
1       -   {$A8,B-,C+,D+,E-,F-,G+,H+,I+,J-,K-,L+,M-,N+,O-,P+,Q-,R-,S-,T-,U-,V+,W+,X+,Y+,Z1}
2       -   program semicolontest;
3       -   
4       -   {$APPTYPE CONSOLE}
5       -   
6       -   uses
7       -     SysUtils;
8       -   
9       -   function isThisUseful1(this : string): boolean;
10      -   var
11      -     i: Integer;
12  2.807175    1,000,000   begin
13  0.761388    1,000,000     result := false;
14  2.133202    1,000,000     try
15  1.403061    1,000,000       if this = ''
16      -       then
17      -         i := 0
18      -       else
19  0.826841    1,000,000         i := 1
20      -     except
21      -       ;
22      -     end;
23  0.735419    1,000,000     result := (i > 0)
24      -   end;
25      -   
26      -   function isThisUseful2(this : string): boolean;
27      -   var
28      -     i: Integer;
29  1.260030    1,000,000   begin
30  0.782293    1,000,000     result := false;
31  0.894420    1,000,000     try
32  0.820860    1,000,000       if this = ''
33      -       then
34      -         i := 0
35      -       else
36  0.873424    1,000,000         i := 1
37      -     except
38      -   
39      -     end;
40  0.778922    1,000,000     result := (i > 0)
41      -   end;
42      -   
43      -   var
44      -     Input : String[20];
45      -     i : Integer;
46      -     Res : Boolean;
47      -   
48  1.167140    1   begin
49      -   
50  0.000036    1      Input := 'abcdefg';
51      -   
52  0.641347    1      writeln('Starting');
53  0.000026    1      for i := 1 to 1000000 do begin
54      -        Res := isThisUseful1(Input);
55      -        Res := isThisUseful2(Input);
56  0.669066    1,000,000      end;
57  0.402661    1      writeln('Done');
58      -      // readln;
59      -   end.
60      -   

Notice that no time is recorded for line 21, the semicolon in the try ...except block, for the simple reason that, as expected, no code is generated for it.

For completeness, the following are disassemblies of isThisUseful1 and isThisUseful2

isThisUseful1:

Address Bytes   Text    Block #
00408620h       [semicolontest.dpr.12] begin    
00408620h   55        push ebp  1
00408621h   8BEC          mov ebp, esp  1
00408623h   83C4F4        add esp, 0FFFFFFF4h ; (-0Ch)  1
00408626h   53        push ebx  1
00408627h   56        push esi  1
00408628h   57        push edi  1
00408629h   8945FC        mov [ebp-04h], eax    1
0040862Ch   8B45FC        mov eax, [ebp-04h]    1
0040862Fh   E87CB7FFFF        call System::LStrAddRef ;(00403DB0h)  1
00408634h   33C0          xor eax, eax  1
00408636h   55        push ebp  1
00408637h   689A864000        push offset @@6   1
0040863Ch   64FF30        push fs:[eax] 1
0040863Fh   648920        mov fs:[eax], esp 1
00408642h       [semicolontest.dpr.13] result := false; 
00408642h   C645FB00          mov byte ptr [ebp-05h], 00h   1
00408646h       [semicolontest.dpr.14] try  
00408646h   33C0          xor eax, eax  1
00408648h   55        push ebp  1
00408649h   6872864000        push offset @@3   1
0040864Eh   64FF30        push fs:[eax] 1
00408651h   648920        mov fs:[eax], esp 1
00408654h       [semicolontest.dpr.15] if this = '' 
00408654h   837DFC00          cmp dword ptr [ebp-04h], 00h  1
00408658h   7507          jnz @@1   1
0040865Ah       [semicolontest.dpr.17] i := 0   
0040865Ah   33C0          xor eax, eax  2
0040865Ch   8945F4        mov [ebp-0Ch], eax    2
0040865Fh   EB07          jmp @@2   2
00408661h       [semicolontest.dpr.19] i := 1   
00408661h       @@1:    
00408661h   C745F401000000        mov dword ptr [ebp-0Ch], 01h  3
00408668h       @@2:    
00408668h   33C0          xor eax, eax  4
0040866Ah   5A        pop edx   4
0040866Bh   59        pop ecx   4
0040866Ch   59        pop ecx   4
0040866Dh   648910        mov fs:[eax], edx 4
00408670h   EB0A          jmp @@4   4
00408672h       @@3:    
00408672h   E9DDACFFFF        jmp System::HandleAnyException ;(00403354h)   5
00408677h       [semicolontest.dpr.21] ;    
00408677h   E8B8AEFFFF        call System::DoneExcept ;(00403534h)  5
0040867Ch       [semicolontest.dpr.23] result := (i > 0)    
0040867Ch       @@4:    
0040867Ch   837DF400          cmp dword ptr [ebp-0Ch], 00h  6
00408680h   0F9F45FB          setnle [ebp-05h]  6
00408684h   33C0          xor eax, eax  6
00408686h   5A        pop edx   6
00408687h   59        pop ecx   6
00408688h   59        pop ecx   6
00408689h   648910        mov fs:[eax], edx 6
0040868Ch   68A1864000        push offset @@7   6
00408691h       @@5:    
00408691h   8D45FC        lea eax, [ebp-04h]    7
00408694h   E8BFB3FFFF        call System::LStrClr ;(00403A58h) 7
00408699h   C3        ret   7
0040869Ah       @@6:    
0040869Ah   E9E1ADFFFF        jmp System::HandleFinally ;(00403480h)    8
0040869Fh   EBF0          jmp @@5   8
004086A1h       @@7:    
004086A1h   8A45FB        mov al, [ebp-05h] 9
004086A4h       [semicolontest.dpr.24] end; 
004086A4h   5F        pop edi   9
004086A5h   5E        pop esi   9
004086A6h   5B        pop ebx   9
004086A7h   8BE5          mov esp, ebp  9
004086A9h   5D        pop ebp   9
004086AAh   C3        ret   9

isThisUseful2:

Address Bytes   Text    Block #
00408620h       [semicolontest.dpr.12] begin    
00408620h   55        push ebp  1
00408621h   8BEC          mov ebp, esp  1
00408623h   83C4F4        add esp, 0FFFFFFF4h ; (-0Ch)  1
00408626h   53        push ebx  1
00408627h   56        push esi  1
00408628h   57        push edi  1
00408629h   8945FC        mov [ebp-04h], eax    1
0040862Ch   8B45FC        mov eax, [ebp-04h]    1
0040862Fh   E87CB7FFFF        call System::LStrAddRef ;(00403DB0h)  1
00408634h   33C0          xor eax, eax  1
00408636h   55        push ebp  1
00408637h   689A864000        push offset @@6   1
0040863Ch   64FF30        push fs:[eax] 1
0040863Fh   648920        mov fs:[eax], esp 1
00408642h       [semicolontest.dpr.13] result := false; 
00408642h   C645FB00          mov byte ptr [ebp-05h], 00h   1
00408646h       [semicolontest.dpr.14] try  
00408646h   33C0          xor eax, eax  1
00408648h   55        push ebp  1
00408649h   6872864000        push offset @@3   1
0040864Eh   64FF30        push fs:[eax] 1
00408651h   648920        mov fs:[eax], esp 1
00408654h       [semicolontest.dpr.15] if this = '' 
00408654h   837DFC00          cmp dword ptr [ebp-04h], 00h  1
00408658h   7507          jnz @@1   1
0040865Ah       [semicolontest.dpr.17] i := 0   
0040865Ah   33C0          xor eax, eax  2
0040865Ch   8945F4        mov [ebp-0Ch], eax    2
0040865Fh   EB07          jmp @@2   2
00408661h       [semicolontest.dpr.19] i := 1   
00408661h       @@1:    
00408661h   C745F401000000        mov dword ptr [ebp-0Ch], 01h  3
00408668h       @@2:    
00408668h   33C0          xor eax, eax  4
0040866Ah   5A        pop edx   4
0040866Bh   59        pop ecx   4
0040866Ch   59        pop ecx   4
0040866Dh   648910        mov fs:[eax], edx 4
00408670h   EB0A          jmp @@4   4
00408672h       @@3:    
00408672h   E9DDACFFFF        jmp System::HandleAnyException ;(00403354h)   5
00408677h       [semicolontest.dpr.21] ;    
00408677h   E8B8AEFFFF        call System::DoneExcept ;(00403534h)  5
0040867Ch       [semicolontest.dpr.23] result := (i > 0)    
0040867Ch       @@4:    
0040867Ch   837DF400          cmp dword ptr [ebp-0Ch], 00h  6
00408680h   0F9F45FB          setnle [ebp-05h]  6
00408684h   33C0          xor eax, eax  6
00408686h   5A        pop edx   6
00408687h   59        pop ecx   6
00408688h   59        pop ecx   6
00408689h   648910        mov fs:[eax], edx 6
0040868Ch   68A1864000        push offset @@7   6
00408691h       @@5:    
00408691h   8D45FC        lea eax, [ebp-04h]    7
00408694h   E8BFB3FFFF        call System::LStrClr ;(00403A58h) 7
00408699h   C3        ret   7
0040869Ah       @@6:    
0040869Ah   E9E1ADFFFF        jmp System::HandleFinally ;(00403480h)    8
0040869Fh   EBF0          jmp @@5   8
004086A1h       @@7:    
004086A1h   8A45FB        mov al, [ebp-05h] 9
004086A4h       [semicolontest.dpr.24] end; 
004086A4h   5F        pop edi   9
004086A5h   5E        pop esi   9
004086A6h   5B        pop ebx   9
004086A7h   8BE5          mov esp, ebp  9
004086A9h   5D        pop ebp   9
004086AAh   C3        ret   9

(I went cross-eyed trying to compare these myself, and wasn't going to waste my time running them past BeyondCompare).

Anyway, I think this shows that as stated by myself and others, the compiler does not generate code for a line containing only a semicolon in a try...except block, and it shouldn't do anywhere else, either, though as noted there are cases where the presence or otherwise of a semicolon changes the semantics of the code and hence the code generated.

MartynA
  • 28,815
  • 3
  • 27
  • 68
0

In addition to Andreas' answer...

There is 1 situation where you definitely (or, at least, most likely) don't want to leave a semi colon, and it's after a "then". The following code will show "B" in the message box.

procedure TForm3.FormCreate(Sender: TObject);
var
  S : String;
begin
  S := 'A';
  if 1 = 2 then;
    S := 'B';

  ShowMessage(S);
end;

From the top of my head, that's the only case where inserting a semi colon changes the code generations by itself.

Ken Bourassa
  • 5,925
  • 1
  • 17
  • 28
  • I've not seen this in code intentionally, thus far.. Delphi since 2003. ironically, started .NET 6 months into Delphi project for a mobile Add-on, done in C#. – Joseph Poirier Feb 18 '20 at 21:22
  • @Ken: And similarly for `for`, `while`, etc. But there is an even scarier example than those. See the update in my answer. – Andreas Rejbrand Feb 18 '20 at 21:24
  • 1
    @JosephPoirier We've had that in our code. Probably an accident. Was like, 15 years old when a static code analysis tool extracted this case from our code. At the time, I was even surprised it compiled. Just like Andreas' case... (semi colon before "else" and the compiler don't complain? O.o) – Ken Bourassa Feb 18 '20 at 21:29
  • The [if {;} Else] inside another [if else] or [case else] scenario.. is totally plausible. I often wrap in [begin - end] on certain items to avoid obscurity.. readability is key for efficiency. – Joseph Poirier Feb 18 '20 at 21:45
  • This doesn't answer the question. – David Heffernan Feb 19 '20 at 15:46
0

I appreciate all the feedback on this post. I would like to summarize some points..

Semi-colons change the way the application compiles. Therefore an application size can change by a minute amount. So, it appears to be a hard or branch-switch terminator in the application. That said, it should probably be at the end of every Try except end; prior to every except or finally, and at the end of every Loop, regardless of what follows to ensure the compile is clean and consistently the same.

But shouldn't be an issue missing from an if-[end] prior to another [end] statement, or as an additional item inside a spot where no other code exists. But probably an extra semicolon inside a loop, is not the best idea ever.

Joseph Poirier
  • 191
  • 1
  • 11