Ask Reuben

Length of Array And Char

Should I use CHAR or STRING? 

Should I use static ARRAY or DYNAMIC ARRAY? 

Can I optimise my program any further?

Many years ago when I first transformed an Informix-4gl application to Genero, one of the things we investigated and actioned was changing CHAR variables to STRING variables, and similarly static ARRAY to DYNAMIC ARRAY‘s.  In particular we paid a lot of attention to CHAR and ARRAY that were an arbitrary length e.g. 1000, and said that if there was not a sound underlying reason for that length to be what it was, then we should consider using a STRING or DYNAMIC ARRAY instead.  With a lot of the code I see on the support desk, that thought process has not occurred.

The type of code I am referring to is code like …

DEFINE where_clause CHAR(1000)
...
CONSTRUCT BY NAME where_clause ON ...

This allocates 1000 bytes of memory, no matter what the length entered is.  From a programming perspective you also have to consider the case of what happens if more than 1000 characters are assigned to that variable.

Similarly for some arrays you might  have

DEFINE arr ARRAY[1000] OF someType.*
...
FOREACH some_cursor INTO arr[i].*

This allocates in memory 1000 times whatever the length of an array member is, irrespective of the number of rows returned.  Again from a programming perspective you have to explicitly consider the case of what happens if an attempt is made to assign more than 1000 rows.

An improvement you can make to your code is to use STRING instead of CHAR, and to use DYNAMIC ARRAY instead of static ARRAY.  These datatypes rather than having a fixed length allow for an unlimited length and allocate memory as required.  That is

DEFINE where_clause STRING

DEFINE arr DYNAMIC ARRAY OF someType.*

This leads to improved memory usage.  With STRING and DYNAMIC ARRAY memory is only allocated as it used, rather than allocating a fixed amount that may or may not be used.

This also leads to more reliable code as you are not encountering situations where you exceed the allocated length of the CHAR or static ARRAY.    You also end up with less code as you don’t have the code trying to avoid these out of bounds errors.  Also with STRING you don’t need to use CLIPPED like you do with CHAR.

You also get access to the methods that are available for STRING, and for DYNAMIC ARRAY’s.  The one you will use most often is the getLength() method which in some instances saves you defining and maintaining another variable just to store the actual length of the CHAR or static ARRAY.

You also get improved performance and functionality as these datatypes are passed by reference and not by value.  For STRING read more here as it is passed by reference, and for arrays compare what happens when a static ARRAY is passed by value, and a DYNAMIC ARARAY is passed by reference.  The performance improvement occurs as large amounts of data are not pushed and popped off a stack in a function call, only the address of the variable is.  This also means that a DYNAMIC ARRAY can be returned from a FUNCTION, something you cannot do with a static ARRAY.

There is one gotcha you have to watch out for with DYNAMIC ARRAY’s.

If you have …

DEFINE da DYNAMIC ARRAY OF someType.*
...
FOREACH some_cursor INTO da[i].*
   LET i = i + 1
END FOREACH
DISPLAY da.getLength()

If the cursor returns 10 rows, what will DISPLAY da.getLength() output?  It will actually output 11.  To understand why, consider the equivalent code using FETCH and a WHILE loop …

OPEN some_cursor
LET i = 1
WHILE TRUE
    FETCH some_cursor INTO da[i].*
    IF status = NOTFOUND THEN
       EXIT WHILE
    END IF
    LET i = i + 1
END WHILE

The FETCH will occur 11 times, once for each of the rows returned, and then one more time to do the last unsuccessful fetch.  The FOREACH does a similar thing.  So a pattern you might adopt is …

DEFINE da DYNAMIC ARRAY OF someType.*
DEFINE row someType.*

FOREACH some_cursor INTO row.*
    LET i = i + 1
    LET da[i].* = row.*
END FOREACH

A simple rule I adopt is that if there is a CHAR variable or a static ARRAY variable, there has to be some logic to the number.  It cannot be some arbitrarily large number e.g. 1000.  One way for that happen is to make that number a constant e.g.

CONSTANT DAYS_IN_WEEK = 7
CONSTANT MONTHS_IN_YEAR = 12

DEFINE daily_sales ARRAY[DAYS_IN_WEEK] OF someType.*
DEFINE monthly_balance ARRAY[MONTHS_IN_YEAR] OF someType.*

… and so aim to only see a CONSTANT used as a length parameter for CHAR and static ARRAY variables.

If there is no number I can justify with some reasoning, then I should be using DYNAMIC ARRAY and STRING.

If I need to justify this, then it is

  • improved memory usage
  • more reliable code as don’t run into possibility of arbitrary length being exceeded.
  • less code as I don’t have to explicitly avoid arbitrary length being exceeded
  • access to methods for STRING and DYNAMIC ARRAY  datatypes
  • pass by reference, not by value