More like a WARNING than a Tip or Trick

Started by MrBcx, June 01, 2025, 02:37:33 PM

Previous topic - Next topic

Robert

Quote from: MrBcx on June 04, 2025, 11:29:14 AMUPDATE:  I'm still assessing this situation. 

Here is a variation on a theme, based on Robert's earlier example:
Existing versions of BCX emits the necessary code to prevent heap leaks but this will crash when run:


? TestFunc (13579)
PAUSE

FUNCTION TestFunc(TheOne)
    DIM AS INTEGER size = 10
    DIM DYNAMIC DynArray[size, size]             
    DynArray[size - 1, size - 1] = TheOne
    FUNCTION = DynArray[size - 1, size - 1]
END FUNCTION


But when I properly introduce a new temporary value, we're all good!


? TestFunc (13579)
PAUSE

FUNCTION TestFunc(TheOne)
    DIM AS INTEGER size = 10
    DIM DYNAMIC DynArray[size, size]             
    DynArray[size - 1, size - 1] = TheOne

    DIM Temp = DynArray[size - 1, size - 1]    ' <<< This
    FUNCTION = Temp                            ' <<< and This
END FUNCTION


CRASHES without a temporary variable:


int TestFunc (int TheOne)
{
  int      size=10;
  int  **DynArray=0;
  {
  size_t dimensions[2] = {(size_t)size, (size_t)size};
  DynArray= (int**)CreateArr (DynArray,sizeof(int),0,2, dimensions);
  }
  DynArray[size-1][size-1]=TheOne;
  if (DynArray) { DestroyArr((void **)DynArray, 2, 1); DynArray=NULL;}
  return DynArray[size-1][size-1];
}

No crash and no leaks when using a temporary variable:


int TestFunc (int TheOne)
{
  int      size=10;
  int  **DynArray=0;
  {
  size_t dimensions[2] = {(size_t)size, (size_t)size};
  DynArray= (int**)CreateArr (DynArray,sizeof(int),0,2, dimensions);
  }
  DynArray[size-1][size-1]=TheOne;
  int     Temp = DynArray[size-1][size-1];
  if (DynArray) { DestroyArr((void **)DynArray, 2, 1); DynArray=NULL; }
  return Temp;
}


Understanding the problem(s) is step 1. 

Teaching BCX to understand the problem(s) is step 2



BCX coders should note that the problem, described by MrBcx above, exists with any numeric (INTEGER, SINGLE or DOUBLE) DYNAMIC array variable dimensioned within the function, when used as FUNCTION = return expression.

MrBcx

#8
UPDATE:  I'm still assessing this situation. 

Here is a variation on a theme, based on Robert's earlier example:
Existing versions of BCX emits the necessary code to prevent heap leaks but this will crash when run:


? TestFunc (13579)
PAUSE

FUNCTION TestFunc(TheOne)
    DIM AS INTEGER size = 10
    DIM DYNAMIC DynArray[size, size]             
    DynArray[size - 1, size - 1] = TheOne
    FUNCTION = DynArray[size - 1, size - 1]
END FUNCTION


But when I properly introduce a new temporary value, we're all good!


? TestFunc (13579)
PAUSE

FUNCTION TestFunc(TheOne)
    DIM AS INTEGER size = 10
    DIM DYNAMIC DynArray[size, size]             
    DynArray[size - 1, size - 1] = TheOne

    DIM Temp = DynArray[size - 1, size - 1]    ' <<< This
    FUNCTION = Temp                            ' <<< and This
END FUNCTION


CRASHES without a temporary variable:


int TestFunc (int TheOne)
{
  int      size=10;
  int  **DynArray=0;
  {
  size_t dimensions[2] = {(size_t)size, (size_t)size};
  DynArray= (int**)CreateArr (DynArray,sizeof(int),0,2, dimensions);
  }
  DynArray[size-1][size-1]=TheOne;
  if (DynArray) { DestroyArr((void **)DynArray, 2, 1); DynArray=NULL;}
  return DynArray[size-1][size-1];
}

No crash and no leaks when using a temporary variable:


int TestFunc (int TheOne)
{
  int      size=10;
  int  **DynArray=0;
  {
  size_t dimensions[2] = {(size_t)size, (size_t)size};
  DynArray= (int**)CreateArr (DynArray,sizeof(int),0,2, dimensions);
  }
  DynArray[size-1][size-1]=TheOne;
  int     Temp = DynArray[size-1][size-1];
  if (DynArray) { DestroyArr((void **)DynArray, 2, 1); DynArray=NULL; }
  return Temp;
}


Understanding the problem(s) is step 1. 

Teaching BCX to understand the problem(s) is step 2


MrBcx

On second thought, the following would be a much easier fix to implement.
It frees memory on entry rather than on exit.  When the app terminates,
any remaining allocated memory would be reclaimed by the OS.


char * Returns (char* TheOne)
{
  char  *BCX_RetStr= {0};
  int    size=10;
  static char  ***DynArray=0;
  {
    size_t dimensions[3] = {(size_t)size, (size_t)size, (size_t)BCXSTRSIZE};
  if (DynArray) DestroyArr((void**)DynArray, 3, 1); 
    DynArray = (char***)CreateArr (DynArray,sizeof(char), 0, 3, dimensions); 
  }
  strcpy(DynArray[size-1][size-1],TheOne);
  BCX_RetStr = BCX_TempStr(strlen(DynArray[size-1][size-1]));
  strcpy(BCX_RetStr,DynArray[size-1][size-1]);

  return BCX_RetStr;
}

MrBcx

Quote from: Robert on June 03, 2025, 04:05:24 PM
FUNCTION Returns$ (TheOne$)
 DIM AS INTEGER size = 10
 DIM DYNAMIC DynArray$[size, size]
 DynArray$[size - 1, size - 1] = TheOne$
 FUNCTION = DynArray$[size - 1, size - 1]
END FUNCTION



Examining the resulting c code exposes an entirely new problem - a memory leak.

Your FUNCTION named Returns() should be translated like this (below) but the portion
that I've highlighted is not being emitted and, I suspect, nothing like it has ever been
emitted in earlier versions.  I'll take a swing at resolving this discovery.


char * Returns (char* TheOne)
{
  char  *BCX_RetStr= {0};
  int    size=10;
  char  ***DynArray=0;

  {
    size_t dimensions[3] = {(size_t)size, (size_t)size, (size_t)BCXSTRSIZE};
    DynArray= (char***)CreateArr (DynArray,sizeof(char),0,3, dimensions); 
  }
 
  strcpy(DynArray[size-1][size-1],TheOne);
  BCX_RetStr = BCX_TempStr(strlen(DynArray[size-1][size-1]));
  strcpy(BCX_RetStr,DynArray[size-1][size-1]);
 
if (DynArray) {
    DestroyArr ((void**) DynArray, 3, 1);
    DynArray = NULL;
  }

 
  return BCX_RetStr;
}

Robert

#5
Quote from: Robert on June 01, 2025, 08:21:41 PMHi MrBcx:
....

Did you check out

d[size1,size2]  <<<  ---  is a dynamic array inside a user-defined function

on both multi-dimensional

DIM DYNAMIC d$[size1, size2]

and single dimension, specified size

DIM DYNAMIC d[size1, size2]

arrays ?
Hi MrBcx:

DYNAMIC arrays of strings declared within a function do not crash when used directly as Function return.

BCX_TempStr takes them out behind the barn and deals with them there.

Regardless, it's a bad idea. An intermediate variable should be used.

Only checked with Pelles C.

Example 1:
DYNAMIC single dimension array of strings of specified length

? Returns$("One")
PAUSE

FUNCTION Returns$ (TheOne$)
 DIM AS INTEGER size = 10
 DIM DYNAMIC DynArray$[size, size] AS CHAR
 DynArray$[size - 1] = TheOne$
FUNCTION = DynArray$[size - 1]
END FUNCTION

Returns:
One

Press any key to continue . . .

Example 2:
DYNAMIC two dimension array of strings

? Returns$("One")
PAUSE

FUNCTION Returns$ (TheOne$)
 DIM AS INTEGER size = 10
 DIM DYNAMIC DynArray$[size, size]
 DynArray$[size - 1, size - 1] = TheOne$
FUNCTION = DynArray$[size - 1, size - 1]
END FUNCTION

Returns:
One

Press any key to continue . . .

Robert

Quote from: MrBcx on June 01, 2025, 10:29:46 PM
Quote from: Robert on June 01, 2025, 08:21:41 PMHi MrBcx:

FUNCTION = SomeExpression statement is just asking for trouble.

That the first and only example, albeit functional, in the BCX Help
FUNCTION ... END FUNCTION
section uses
FUNCTION = SomeExpression,
is not intended to lead BCX users into bad habits.

Nevertheless, the example will be changed.

Did you check out

d[size1,size2]  <<<  ---  is a dynamic array inside a user-defined function

on both multi-dimensional

DIM DYNAMIC d$[size1, size2]

and single dimension, specified size

DIM DYNAMIC d[size1, size2]

arrays ?

Robert --

The following code is what I was working on when I discovered the issue that I described earlier.
The issue affected FUNCTION STRING_SIMILARITY().  The code seems to be rock solid, now that I've
accounted for how my local dynamic array (LevMatrix [#,#]) gets released.


DIM score AS DOUBLE
score = STRING_SIMILARITY("Calculate minimum of three operations", "Calculate minimum of the operations")
PRINT "Similarity Score: ", PERCENT(score)
PAUSE


FUNCTION PERCENT (Fraction AS DOUBLE) AS STRING
    DIM result$
    sprintf(result$, "%.1f%%", Fraction * 100.0)
    FUNCTION = result$
END FUNCTION



FUNCTION STRING_SIMILARITY (s1$, s2$) AS DOUBLE
    '===========================================================
    ' This function uses the Levenshtein distance algorithm but
    ' returns a value expressing the percentage of similarity of
    ' the string arguments.
    ' If either Arg LEN > BCXSTRSIZE, function returns -1
    ' 0 = no similarity, 1 = Args are equal, and everything
    ' else is a percentage of similarity between 0.0 to 1.0
    '===========================================================
    DIM AS INT len1, len2, i, j, cost, maxLen
    DIM AS CHAR PTR p1, p2

    p1 =  s1$
    p2 =  s2$

    len1 = LEN(s1$)
    len2 = LEN(s2$)

    maxLen = MAX(len1, len2)

    ' Handle edge cases
    IF len1 = 0 AND len2 = 0 THEN FUNCTION = 1.0
    IF len1 = 0 OR len2 = 0 THEN FUNCTION = 0.0
    IF s1$ = s2$ THEN FUNCTION = 1.0
    IF len1 > BCXSTRSIZE OR len2 > BCXSTRSIZE THEN FUNCTION = -1

    ' Create 2D Levenshtein matrix
    REDIM LevMatrix [len1+1, len2+1] AS INTEGER ' Make the matrix dynamic

    ' Initialize first row and column
    FOR i = 0 TO len1
        LevMatrix[i, 0] = i
    NEXT
    FOR j = 0 TO len2
        LevMatrix[0, j] = j
    NEXT

    ' Fill the matrix
    FOR i = 1 TO len1
        FOR j = 1 TO len2
            ' Compare characters using pointers
            IF p1[i-1] = p2[j-1] THEN
                cost = 0
            ELSE
                cost = 1
            END IF
            ' Calculate minimum of three operations: deletion, insertion, and substitution
            LevMatrix[i, j] = MIN(LevMatrix[i-1, j] + 1, MIN(LevMatrix[i, j-1] + 1, LevMatrix[i-1, j-1] + cost))
        NEXT
    NEXT

    '============================================================
    ' !!! SAVE THE RESULT BEFORE BCX DESTROYS THE LOCAL ARRAY !!!
    '============================================================

    DIM result AS DOUBLE
    ' Convert distance to similarity (0.0 to 1.0)
    result = 1.0 - CDBL(LevMatrix[len1, len2]) / maxLen
    FUNCTION = result
END FUNCTION



Thanks for that. A great help to understanding the problem.

As you said "we need to save the value of our dynamically dimensioned variables inside a temporary variable before they get destroyed and then use the temporary variable inside our FUNCTION ="


MrBcx

Quote from: Robert on June 01, 2025, 08:21:41 PMHi MrBcx:

FUNCTION = SomeExpression statement is just asking for trouble.

That the first and only example, albeit functional, in the BCX Help
FUNCTION ... END FUNCTION
section uses
FUNCTION = SomeExpression,
is not intended to lead BCX users into bad habits.

Nevertheless, the example will be changed.

Did you check out

d[size1,size2]  <<<  ---   is a dynamic array inside a user-defined function

on both multi-dimensional

DIM DYNAMIC d$[size1, size2]

and single dimension, specified size

DIM DYNAMIC d[size1, size2]

arrays ?

Robert --

The following code is what I was working on when I discovered the issue that I described earlier.
The issue affected FUNCTION STRING_SIMILARITY().  The code seems to be rock solid, now that I've
accounted for how my local dynamic array (LevMatrix [#,#]) gets released.


DIM score AS DOUBLE
score = STRING_SIMILARITY("Calculate minimum of three operations", "Calculate minimum of the operations")
PRINT "Similarity Score: ", PERCENT(score)
PAUSE


FUNCTION PERCENT (Fraction AS DOUBLE) AS STRING
    DIM result$
    sprintf(result$, "%.1f%%", Fraction * 100.0)
    FUNCTION = result$
END FUNCTION



FUNCTION STRING_SIMILARITY (s1$, s2$) AS DOUBLE
    '===========================================================
    ' This function uses the Levenshtein distance algorithm but
    ' returns a value expressing the percentage of similarity of
    ' the string arguments.
    ' If either Arg LEN > BCXSTRSIZE, function returns -1
    ' 0 = no similarity, 1 = Args are equal, and everything
    ' else is a percentage of similarity between 0.0 to 1.0
    '===========================================================
    DIM AS INT len1, len2, i, j, cost, maxLen
    DIM AS CHAR PTR p1, p2

    p1 =  s1$
    p2 =  s2$

    len1 = LEN(s1$)
    len2 = LEN(s2$)

    maxLen = MAX(len1, len2)

    ' Handle edge cases
    IF len1 = 0 AND len2 = 0 THEN FUNCTION = 1.0
    IF len1 = 0 OR len2 = 0 THEN FUNCTION = 0.0
    IF s1$ = s2$ THEN FUNCTION = 1.0
    IF len1 > BCXSTRSIZE OR len2 > BCXSTRSIZE THEN FUNCTION = -1

    ' Create 2D Levenshtein matrix
    REDIM LevMatrix [len1+1, len2+1] AS INTEGER ' Make the matrix dynamic

    ' Initialize first row and column
    FOR i = 0 TO len1
        LevMatrix[i, 0] = i
    NEXT
    FOR j = 0 TO len2
        LevMatrix[0, j] = j
    NEXT

    ' Fill the matrix
    FOR i = 1 TO len1
        FOR j = 1 TO len2
            ' Compare characters using pointers
            IF p1[i-1] = p2[j-1] THEN
                cost = 0
            ELSE
                cost = 1
            END IF
            ' Calculate minimum of three operations: deletion, insertion, and substitution
            LevMatrix[i, j] = MIN(LevMatrix[i-1, j] + 1, MIN(LevMatrix[i, j-1] + 1, LevMatrix[i-1, j-1] + cost))
        NEXT
    NEXT

    '============================================================
    ' !!! SAVE THE RESULT BEFORE BCX DESTROYS THE LOCAL ARRAY !!!
    '============================================================

    DIM result AS DOUBLE
    ' Convert distance to similarity (0.0 to 1.0)
    result = 1.0 - CDBL(LevMatrix[len1, len2]) / maxLen
    FUNCTION = result
END FUNCTION


Robert

Hi MrBcx:

FUNCTION = SomeExpression statement is just asking for trouble.

That the first and only example, albeit functional, in the BCX Help
FUNCTION ... END FUNCTION
section uses
FUNCTION = SomeExpression,
is not intended to lead BCX users into bad habits.

Nevertheless, the example will be changed.

Did you check out

d[size1,size2]  <<<  ---   is a dynamic array inside a user-defined function

on both multi-dimensional

DIM DYNAMIC d$[size1, size2]

and single dimension, specified size

DIM DYNAMIC d[size1, size2]

arrays ?

MrBcx

I stumbled upon this scenario today.  It involves using a dynamically dimensioned
variable inside a user defined function.  More specifically, using that kind of
variable inside a "FUNCTION = SomeExpression". 

BCX intentionally destroys LOCAL dynamically dimensioned variables before exiting
user-defined SUBs and FUNCTIONs.  That means, if one tries to use one of those variables
after it has been destroyed, you will generate a heap memory violation
.  Therefore,
it is important that we not do that.  Instead, we need to save the value of our
dynamically dimensioned variables inside a temporary variable before they get destroyed
and then use the temporary variable inside our FUNCTION = SomeExpression statement.

Here is a bit of a map that I put together today that shows the differences:


Consider this:


d[size1,size2]  <<<  ---   is a dynamic array inside a user-defined function

That function tail looks like this:

FUNCTION = 1.0 - CDBL(d[len1, len2]) / maxLen
END FUNCTION

And BCX emits this code:

if (d) { DestroyArr((void **)d, 2, 1); d=NULL; }
return 1.0-CDBL(d[len1][len2])/maxLen;
----------------^ d[#,#] was destroyed in the preceding line

and will cause the app to crash - heap memory violation.



Here is how to prevent that scenario:



d[size1,size2]  <<<  ---   is a dynamic array inside a user-defined function

'============================================================
' !!! SAVE THE RESULT BEFORE BCX DESTROYS THE LOCAL ARRAY !!!
'============================================================

DIM result AS DOUBLE                 
result = 1.0 - CDBL(d[len1, len2]) / maxLen
FUNCTION = result
end function

BCX emits this code:

double result= {0};
result = 1.0-CDBL(d[len1][len2])/maxLen;
if (d) { DestroyArr((void **)d, 2, 1); d=NULL; }
return result;
}

All good!