• Welcome to Jose's Read Only Forum 2023.
 

The SELECT CASE Gotcha!

Started by Donald Darden, July 31, 2007, 08:40:20 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Donald Darden

SELECT CASE is often useful in coding because it gives you a nice way to test for
several possible outcomes based on a single condition.  But there is a hidden
aspect of using SELECT CASE that can sometimes reach up and bite you, as once happened to me.

To illustrate what happened, let;s write a simple case that will check for an error
condition:

    SELECT CASE ERRCLEAR
    CASE 0
        'no error, so we do something here
     CASE ELSE
        'we got an error, so we want to trap for it here
     END SELECT

Now we just have to produce an error, and see what happened.  We could do a
divide by zero for example, or we could try to open a nonexistant file to read it,
or we can assign a value to ERR for testing.  I will leave it to you to decide what
error to check for.

Now technically, there are two ways the SELECT CASE statement could work.
The first is that the error number can be retained and reused for each subsequent CASE statement in the SELECT CASE structure.  We would consider
this the static form.  The other way is that the code that implements SELECT CASE would perform the ERRCLEAR operation repeatedly, once for each CASE statement that follows in the same structure.  We would classify this a a dynamic approach.

The question is, which method does PowerBasic employ?  If the static method,
then the returned error number would be rechecked with each CASE statement.
until we take the designated branch.  But if they do it dynamically, then the ERRCLEAR function would cause the error to be cleared after the first check
(and if non-zero it would fail the CASE 0 test).  But then the next check of the
error for the CASE ELSE condition would then see the cleared ERR state instead,
and it would also fail.  In other words, the error would escape undetected through our checks becaused the tested aspct was redone rather than retained,
and the outcome was allowed to change in the interum.

You can easily test for yourself which condition would result with any compiler
that supports SELECT CASE when used in conjunction with any function that
can return a different result after the first test.  If the function involves a
condition that can change in this manner, you may be at risk of having something slip past your SELECT CASE code unexpectedly.

The obvious and easy way to avoid this is to first assign the result of that
function to a variable, then use that variable in its place with the SELECT CASE
statement.  For instance:

    a = ERRCLEAR
    SELECT CASE a
    CASE 0
        'no error occurred
     CASE ELSE
        'here we detect an error
      END SELECT


Note that if we had not begun with CASE 0 in this example, it would appear that the first coding sequence would have worked correctly.  However, if we were trying to test for the exact error that occurred, the results would have been
lost after the first use of ERRCLEAR, and we would likely not know what it had
been.

There is another reason for assigning the results to a variable before using
SELECT CASE, which is that testing a variable repeatedly would be faster than
performing the computations called for by a function or expression.

Note that for SELECT CASE statements involving evaluated strings, the best
you can do is to render a final string that can be then checked as needed by
each CASE condition, so that this:

     aa$=RIGHT$("000000"+LTRIM$STR$(n))+".00",9)
     SELECT CASE aa$

would be faster than

    SELECT CASE  RIGHT$("000000"+LTRIM$(STR$(n))+".00",9)

when multiple CASE conditions have to be evaluated.

José Roca

 
Quote
There is another reason for assigning the results to a variable before using
SELECT CASE, which is that testing a variable repeatedly would be faster than
performing the computations called for by a function or expression.

I Strongly doubt that PB will perform the computation each time. Most likely, it will store the value of the expression in a temporary variable.

Donald Darden

Perhaps.  I certainly would recommend that the compiler allocate a temporary variable for the purpose, but I've noted in the past that this depends on the nature of the compiler, and it not anything we control.  With the example code I posted. it would be easy enough to test. right?  I could have tested it myself, but it's better if someone interested in the possible outcome did it for themselves.  I will say this:  It has proved a problem in the past, spoiling one program I was developing, and took hours to run down and pinpoint.  Then figuring out the cause itself proved to be a challenge. 

Theo Gottwald

#3
The compiler definitely calculates the value after the Line

SELECT CASE (a&+5)

and stores it in a temporary variable, which is used for all of the compares.

In a disassembly you can see that if you write,

REGISTER a&
SELECT CASE AS LONG a&


even then a& is NOT been kept in the Register, but a temporary variable is been used for the SELECT CASE comparisons.
In the example below this is: DWORD PTR [EBP-40]

As you can see, the compiler puts the actual result from the CASE into EAX and then always compares with the temporary variable.
Its the same if you write:

FOR R01=0 to R02+5

Also in that case, you can see that the comparing is with a temporary variable:

402423 3B856CFFFFFF           CMP EAX, DWORD PTR [EBP+FFFFFF6C]
402429 7EF3                   JLE SHORT L40241E


the calculation is done only once before the Loop is entered.
Actually I think this is a general behavior.

Example:

SUB TestfuncA()
REGISTER R01 AS LONG,R02 AS LONG
!NOP
R01=100
SELECT CASE AS LONG R01
     !NOP
    CASE 1: REM
     !NOP
    CASE 2: REM
     !NOP
  CASE ELSE
     !NOP
    REM
    !NOP
END SELECT
    ! NOP
  FOR R02=0 TO R01+5
    !NOP
  NEXT
    !NOP    

' will be:

4023D0 90                     NOP
4023D1 C7C664000000           MOV ESI, DWORD 00000064
4023D7 8975C0                 MOV DWORD PTR [EBP-40], ESI
4023DA 90                     NOP
4023DB B801000000             MOV EAX, DWORD 00000001
4023E0 3B45C0                 CMP EAX, DWORD PTR [EBP-40]
4023E3 0F8506000000           JNZ L4023EF
4023E9 90                     NOP
4023EA E916000000             JMP L402405
4023EF B802000000             MOV EAX, DWORD 00000002
4023F4 3B45C0                 CMP EAX, DWORD PTR [EBP-40]
4023F7 0F8506000000           JNZ L402403
4023FD 90                     NOP
4023FE E902000000             JMP L402405
402403 90                     NOP
402404 90                     NOP
402405 90                     NOP

' --------------   FOR R02=0 TO R01+5
402406 B805000000             MOV EAX, DWORD 00000005
40240B 03C6                   ADD EAX, ESI
40240D 89856CFFFFFF           MOV DWORD PTR [EBP+FFFFFF6C], EAX
402413 C7C700000000           MOV EDI, DWORD 00000000
402419 E903000000             JMP L402421
'------------------------------------------------
40241E 90                     NOP

' -------------- NEXT
40241F FFC7                   INC EDI
402421 8BC7                   MOV EAX, EDI
402423 3B856CFFFFFF           CMP EAX, DWORD PTR [EBP+FFFFFF6C]
402429 7EF3                   JLE SHORT L40241E
'------------------------------------------------

40242B 90                     NOP



Charles Pegge

In Freebasic you have the option of doing constant cases or expression cases.

SELECT CASE AS CONST a ...

http://www.freebasic.net/wiki/wikka.php?wakka=KeyPgSelectcase

José Roca

 
In PB you have SELECT CASE AS LONG | CONST | CONST$.

Theo Gottwald

QuoteIn Freebasic you have the option of doing constant cases or expression cases.

SELECT CASE AS CONST a ...

I'd be more impressed, if you tell me that FB compiles it using registers for both parts, not only EAX.
But I assume that this is general state-of compiler-programming.



' Would prefer:

          MOV EAX, DWORD 00000001
          CMP EAX, ESI
          JNZ L4023EF
instead of:

          MOV EAX, DWORD 00000001
          CMP EAX, DWORD PTR [EBP-40]
          JNZ L4023EF



But then, does FB allow the custom Allocation of REGISTER-Variables?
I like the REGISTER Statement for Optimizations, did not see it in FB.
Is it there?

Charles Pegge

I assume that FB leaves all its low level compilation decisions to the GCC compiler. I notice that type checking is more rigorous in FB than PB especially with pointers; very C++ like. Can make some coding tasks awkward.