Announcement

Collapse
No announcement yet.

Confessions of a sloppy programmer

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Confessions of a sloppy programmer

    Two more traps that I have fallen into recently that might help someone else avoid the same pitfalls.:

    1.)

    No error message is generated by the code below but the array is corrupted. It took a bit of finding. The bad code seemed to work OK under PBDLL 5 but not under PBDLL 6. DIM should of course have been REDIM.

    '.....................................
    FUNCTION PBMAIN() AS LONG
    GLOBAL mystring AS STRING*10
    .
    call myprocedure(mystring)
    .
    call myprocedure(mystring)

    END FUNCTION
    ........................................
    SUB myprocedure(mystring AS STRING*10)
    .
    .
    DIM mystring(100)
    .
    END SUB


    2. Logic shortcuts we frequently use can become second nature to us so we often do not check through the code carefully enough as I was recently caught out.

    To check if a string is not empty we often use the following:
    Code:
    IF LEN(mystring) THEN
    .
    END IF
    To check if a substring is in a main string we often use:
    Code:
    IF INSTR(mainstring,mystring) THEN
    .
    END IF
    However, these cannot be safely combined with an AND to test whether both conditions are satisfied eg.
    Code:
    IF LEN(mystring) AND INSTR(mainstring,mystring) THEN
    .
    END IF
    If LEN(mystring) returned 10 and INSTR(mainstring,mystring) returned 1 then:

    LEN(mystring) AND INSTR(mainstring,mystring) actually returns 0 even though both our requirements are satisfied because of the way that the AND operator works.

    We need to use a structure like that below with both components returning -1:

    Code:
    IF LEN(mystring)>0 AND INSTR(mainstring,mystring)>0 THEN
    .
    END IF
    Regards,

    Bern

    ------------------
    Bern
    www.insighttrading.com.au

    [This message has been edited by Bernard Chapman (edited August 26, 2001).]
    Bern


    http://www.insighttrader.com.au

  • #2
    I'm afraid that I have to ask for some clarifications here, Bernard, because your comments do not match the behavior of PowerBASIC as you indicate.

    <U>Point 1:</U> I tried your code and I don't see any corruption occuring at all, whether you use DIM or REDIM... no difference at all. In either case, your array is a LOCAL, and your scalar string is global. No confusion seems evident. Maybe if you could post or email the exact code you used to confirm the problem, I'de be very interested in seeing it.

    Note that using non-unique names between LOCAL and GLOBAL variables is quite legal and possible, but not recommended as it leads to scope confusion (of the programmer, not the compiler! ).

    <U>Point 2:</U> Your real test code must have included parenthesis around the terms which prevents short-circuit evaluation from occurring... your example, as posted, definitely uses short-circuit evaluation of the AND, not bitwise. Therefore your comments do not match the code you posted. Sorry!

    That said, if you have code that shows this to be incorrect, then post or email it too...

    Thanks!




    ------------------
    Lance
    PowerBASIC Support
    mailto:[email protected][email protected]</A>
    Lance
    mailto:[email protected]

    Comment


    • #3
      Lance,

      You are right on both counts. I have been even sloppier than I originally thought:

      2. I did have brackets around:

      LEN(mainstring) AND INSTR(mainstring,mystring)

      as part of a much more complex logical expression. When I simplified it for illustration I left the brackets out. The code below hits the first "hit" message box but not the second so the IF statements are different.


      Code:
      #COMPILE EXE
      
      FUNCTION PBMAIN() AS LONG
      LOCAL mainstring AS STRING, mystring AS STRING
      
      mainstring=" ABC DEF GHI JKL"
      
      mystring = " ABC "
      
      MSGBOX STR$(LEN(mainstring) AND INSTR(mainstring,mystring))
      
      IF LEN(mainstring) AND INSTR(mainstring,mystring) THEN
        MSGBOX "First hit"
      END IF
      
      IF (LEN(mainstring) AND INSTR(mainstring,mystring)) THEN
        MSGBOX "Second hit"
      END IF
      
      END FUNCTION
      However, I am still a bit perplexed as the syntax as described in the documentation states:

      Code:
      IF integer expression [,] THEN
      	{statements}
      .
      .
      END IF
      Remarks: In executing IF block statements, the truth of the integer expression in the initial IF statement is checked first.
      .
      I would have thought that the integer expression in the first case was:

      LEN(mainstring) AND INSTR(mainstring,mystring)

      and in the second case was:

      (LEN(mainstring) AND INSTR(mainstring,mystring))

      both of which should evaluate to the same integer.

      If this not the case I would be interested to learn of the way that "short circuit" evaluation occurs. I do not recall coming across that one before.


      1. Apologies as again you are correct. The simplified pseudocode I posted previously left out the all important brackets on the GLOBAL array MYSTRING and furthermore I should not have had the array in the argument list of myprocedure(). It should have read (in more detail):

      Code:
      '.....................................
      FUNCTION PBMAIN() AS LONG
      GLOBAL mystring() AS STRING*10
      .
      call myprocedure()
      .
      call myprocedure()
      
      END FUNCTION
      ........................................
      SUB myprocedure()
      LOCAL ns AS LONG
      .
      {calculate a new value of ns}
      .
      DIM mystring(ns)
      .
      {fill the string array}
      .
      END SUB
      I should say that the corruption of the array was only partial. Some elements of the array were fine while others had other characters from other arrays including nulls. For the corruption to occur the new value of ns before the second DIM has to be different from the previous one.

      Substituting REDIM for DIM fixed the problem completely.

      This was unequivocally incorrect code and I am surprised that it worked at all. But if you are still interested I could forward you the actual code.

      I appreciate that you have taken the time to field questions of this nature and apologise that my original posting was incorrect.

      Regards,

      Bern

      ------------------
      Bern
      www.insighttrading.com.au

      [This message has been edited by Bernard Chapman (edited August 28, 2001).]
      Bern


      http://www.insighttrader.com.au

      Comment


      • #4
        Regarding the DIM vs REDIM "problem"... it looks to me like you simply had a variable scope problem.

        The "DIM ArrayName()" statement does not include a scope clause, so it defaults to LOCAL scope. However, when you use REDIM, then the compiler picks up on the previously declared GLOBAL array of the same name.

        What you should have done was use the format thus:

        DIM arrayname(isize&) AS GLOBAL STRING

        ...this explicitly sets the scope as GLOBAL. It is advisable to use that format in the REDIM statement too, to ensure that both the compiler and you are 100% in agreement as to the scope.

        When I get back to my DEV PC, I'll dig up some information for you on how short-circuit evaluation works with the AND and OR operators in IF/WHILE/DO statements.


        ------------------
        Lance
        PowerBASIC Support
        mailto:[email protected][email protected]</A>
        Lance
        mailto:lancee[email protected]

        Comment


        • #5
          Lance,

          Thanks for the suggestions. I tried various things and here are the results:

          DIM AS STRING leads to a corrupted array
          DIM AS GLOBAL STRING also leads to a corrupted array

          REDIM AS STRING is fine and
          REDIM AS GLOBAL STRING is also fine

          Since the array is scoped as GLOBAL outside of the SUB and not scoped at all inside of the SUB it would be assumed to be the GLOBAL array already declared.

          Seems like you cannot DIM an array again without erasing it first (as you would expect) otherwise the array corrupts.

          Regards,

          Bern


          ------------------
          Bern

          www.insighttrading.com.au
          Bern


          http://www.insighttrader.com.au

          Comment


          • #6
            There is another aspect that we need to consider here... the DIM statement is probably acting as a "scanned" statement... telling the compiler about the array for the compilation process, but having no effect at runtime.

            Comparitively, REDIM is an executed statement that will create an array (or erase and recreate an existing array) at runtime.

            We would really need to see the exact code to see what you mean by "corrupt" (and explain the behavior that you are seeing), but I'm picking that the code is expected a resized array without verifying the array boundaries.

            Can you post compilable code that demonstrates the effect? However, before doing so, add some code to check the UBOUND() of the array at the point where you think the array corruption is occuring and retest the code.

            Thanks!



            ------------------
            Lance
            PowerBASIC Support
            mailto:[email protected][email protected]</A>
            Lance
            mailto:[email protected]

            Comment


            • #7
              One more thing, just to clarify the "scanned" statement behavior slightly...

              Consider this code:
              Code:
              FUNCTION MyFunc&()
                DIM A(100) AS GLOBAL LONG
                ...
              END FUNCTION
              Now, if the DIM statement cleared & redimensioned the array every time the Function was called, there would effectively be no retained {global} data because the array would be cleared at the start of each call.

              What actually happens is that the array is created just once (at runtime), and the array is maintained between each call without being cleared/redimensioned or cleared.

              I hope this helps!

              ------------------
              Lance
              PowerBASIC Support
              mailto:[email protected][email protected]</A>
              Lance
              mailto:[email protected]

              Comment


              • #8
                Originally posted by Bernard Chapman:
                I would have thought that the integer expression in the first case was:

                LEN(mainstring) AND INSTR(mainstring,mystring)

                and in the second case was:

                (LEN(mainstring) AND INSTR(mainstring,mystring))

                both of which should evaluate to the same integer.
                In early-out evaluation, or short-circuit evaluation, the point is that
                the AND expression doesn't evaluate to an integer. Such evaluation takes
                place in conditional statements (IF, WHILE, etc).

                In most BASICs, there is no early-out evaluation, and conditionals are
                always evaluated fully. This is only guaranteed to be true in PowerBASIC
                if you enclose the conditional in parentheses. Otherwise, the expression
                is handled as Boolean (rather than bitwise) in regards to ANDs, allowing
                certain optimizations.

                Consider the following:

                IF LEN(mainstring) AND INSTR(mainstring,mystring) THEN

                In PowerBASIC, if the length of mainstring is zero... since zero
                ANDed with anything is going to be zero, the compiler knows it doesn't
                have to look any further. At this point, there is no need to evaluate the
                INSTR expression at all-- the result of the IF is going to be FALSE. In
                essence, the IF here translates as:

                IF LEN(mainstring)THEN IF INSTR(mainstring,mystring) THEN

                ...which is not only a convenient optimization, it can be very important
                in cases where the second conditional must not be tested if the first
                failed. For example:

                IF pSource AND @pSource <> 32 THEN

                ...with traditional BASICs, the whole expression would be evaluated,
                causing a GPF if pSource was a null pointer. Of course, traditional BASICs
                don't have pointers, either, but that's the general idea.


                ------------------
                Tom Hanlin
                PowerBASIC Staff

                Comment


                • #9
                  Tom,

                  Many thanks for that in depth explanation. PB has certainly added flexibility here as elsewhere.

                  I don't know whether I just have an early version of the manual but it might be worth adding something along these lines to the description of the IF statments.

                  I appreciate the response.

                  Regards,

                  Bern

                  ------------------
                  Bern

                  www.insighttrading.com.au
                  Bern


                  http://www.insighttrader.com.au

                  Comment


                  • #10
                    Lance,

                    Further to the REDIM question above:

                    Code:
                    #COMPILE EXE
                    #DEBUG ERROR ON
                    
                    GLOBAL myarray() AS STRING*15,ns AS LONG
                    
                    FUNCTION PBMAIN() AS LONG
                    LOCAL i AS LONG,txt AS STRING
                    
                    ns=10
                    
                    CALL myprocedure
                    
                    txt=""
                    FOR i=1 TO ns
                        txt=txt+"*"+myarray(i)+"*"+$CRLF
                    NEXT
                    
                    MSGBOX txt
                    
                    ns=20
                    
                    CALL myprocedure
                    
                    txt=""
                    FOR i=1 TO ns
                        txt=txt+"*"+myarray(i)+"*"+$CRLF
                    NEXT
                    
                    MSGBOX txt
                    
                    END FUNCTION
                    '....................................................................
                    SUB myprocedure
                       LOCAL i AS LONG
                    
                       REDIM myarray(ns)
                    
                       FOR i=1 TO ns
                           myarray(i)="myarray"+STR$(i)
                       NEXT
                    END SUB
                    The above code works perfectly. If you replace REDIM with DIM the second message box shows that something is different with the elements of the array from 11 to 20.

                    The difference is that they contain some nulls that prevent the display of what comes after in the txt string. These could be seen if you were to PRINT the elements to file rather than displaying on the message box.

                    If you change the #DEBUG ERROR to OFF you will get a GPF which is reassuring as the code is defective.

                    Regards,

                    Bern

                    ------------------
                    Bern

                    www.insighttrading.com.au
                    Bern


                    http://www.insighttrader.com.au

                    Comment


                    • #11
                      Bern,

                      The reason your example doesn't work with DIM is that the array can only
                      be dimensioned once, and it was dimensioned at ns = 10. When elements
                      of the array greater than 10 are referenced in the second call, then a GPF
                      error occurs. If ns is reset to something less than 10, say 8, it works fine.

                      ------------------


                      [This message has been edited by Charles Dietz (edited August 29, 2001).]

                      Comment


                      • #12
                        Charles,

                        You are right. Immediately I posted this code I tried Lance's suggestion of UBOUND() immediately after the call to the procedure that redimensioned it. With REDIM, UBOUND() gave 10 and 20. With DIM it was 10 and 10.

                        I tried to edit my posting to include this information but infuriatingly I could not get back on to the site except at incredibly slow speed that effectively stopped downloading of pages.

                        By the way does anyone know how to implement word wrap in this edit box I am typing in in this BBS.

                        Regards,

                        Bern


                        ------------------
                        Bern

                        www.insighttrading.com.au
                        Bern


                        http://www.insighttrader.com.au

                        Comment

                        Working...
                        X