The Usage of Variables in BCX

In BCX, as in the C language, before a variable is used to store a scalar or array value, it MUST be named and allocated storage space by declaring it using a DIM, LOCAL, GLOBAL, SHARED or STATIC statement. Also the data type of the variable should be indicated. If the data type of a variable is not indicated, BCX assumes that the variable is an integer.

Variable data type declaration suffix

The data type of a variable can be indicated by a data type declaration suffix (%, !, # or $) appended to the variable name.

A data type-declaration suffix also can be expressed using the AS keyword in combination with the DIM statement, for example,

 
 DIM VarSC AS UCHAR

would dimension VarSC as an unsigned char.

Here is a fundamental list showing how some data types can be declared using the AS keyword.

Dimensioning Integers

 
 Syntax: DIM a AS CHAR
  Purpose:  Allocates space for char variable named a
  Remarks: In the Borland, Digital Mars, LCCWin32, Microsoft, MinGW, and Pelle's
  C compilers, the char data type is a signed char which can hold a value in the range -128 to 127. 
  In the Open Watcom C compiler, the default char data type is an unsigned char which can hold a value
  in the range 0 to 255, however, this default can be changed to a signed char by using the command line flag -j.
  For MinGW, the signed char default can be changed to unsigned with the -funsigned-char command line flag. 
  In the Microsoft compiler, the signed char default can be changed to unsigned with the /J command line flag.
  the Pelle's C compiler /J option makes the default char type unsigned.
 
 Syntax: DIM a AS UCHAR
  Purpose:  Allocates space for unsigned char variable named a
  Remarks: Unsigned char can hold Minimum 0 Maximum 255
            DO NOT USE Syntax: DIM a AS UNSIGNED CHAR
 
 Syntax: DIM a AS SHORT
  Purpose:  Allocates space for signed short variable named a
  Remarks: Signed short can hold Minimum -32768 Maximum 32767
            DO NOT USE Syntax: DIM a AS SIGNED SHORT
 
 Syntax: DIM a AS USHORT
  Purpose:  Allocates space for unsigned short variable named a
  Remarks: Unsigned short can hold Minimum 0 Maximum 65535
            DO NOT USE Syntax: DIM a AS UNSIGNED SHORT
 
 Syntax 1: DIM a
 Syntax 2: DIM a%
 Syntax 3: DIM a AS INTEGER
  Purpose:  Allocates space for integer variable named a
  Remarks: Integer can hold Minimum -2147483648 Maximum 2147483647
            DO NOT USE Syntax: DIM a AS SIGNED INTEGER
 
 Syntax: DIM a AS UINT
  Purpose:  Allocates space for unsigned integer variable named a
  Remarks: Unsigned integer can hold Minimum 0 Maximum 4294967295
            DO NOT USE Syntax: DIM a AS UNSIGNED INTEGER
 
 Syntax: DIM a AS LONG
  Purpose:  Allocates space for signed long variable named a
  Remarks: Signed long can hold Minimum -2147483648 Maximum 2147483647
            DO NOT USE Syntax: DIM a AS SIGNED LONG
 
 Syntax: DIM a AS ULONG
  Purpose:  Allocates space for unsigned long variable named a
  Remarks: Unsigned long can hold Minimum 0 Maximum 4294967295
            DO NOT USE Syntax: DIM a AS UNSIGNED LONG
 
 Syntax: DIM a AS LONGLONG
  Purpose:  Allocates space for long long integer variable named a
  Remarks: Long long integer can hold Minimum -9223372036854775807 Maximum 9223372036854775807
            DO NOT USE Syntax: DIM a AS LONG LONG
 
 Syntax: DIM a AS ULONGLONG
  Purpose:  Allocates space for unsigned long long integer variable named a
  Remarks: Unsigned long long integer can hold Minimum 0 Maximum 18446744073709551615
            DO NOT USE Syntax: DIM a AS UNSIGNED LONG LONG

Dimensioning Floating Point Numbers

 
 Syntax: DIM E!
  Purpose:  Allocates space for one SINGLE floating point variable named E

 Syntax: DIM E AS FLOAT
  Purpose:  Allocates space for one SINGLE floating point variable named E

 Syntax: DIM E AS SINGLE
  Purpose:  Allocates space for one SINGLE floating point variable named E

 Syntax: DIM F#
  Purpose:  Allocates space for one DOUBLE floating point variable named F

 Syntax: DIM F AS DOUBLE
  Purpose:  Allocates space for one DOUBLE floating point variable named F

 Syntax: DIM F AS LDOUBLE
  Purpose:  Allocates space for one LONG DOUBLE floating point variable named F

Dimensioning Strings

 
 Syntax: DIM A0$
  Purpose:  Allocates space for a default(2048 byte) string

 Syntax: DIM A0 AS STRING
  Purpose:  Allocates space for a default(2048 byte) string

 Syntax: DIM A1 AS STRING * 4096
  Purpose:  Allocates space for a 4096 byte string

 Syntax: DIM A2 AS CHAR
  Purpose:  Allocates space for a single character

 Syntax: DIM A3 [1024] AS CHAR
  Purpose:  Allocates space for a 1024 byte string

More than one variable can be dimensioned on a single line. For example,

 
 
 DIM a%, b%, c%

and

 

 DIM a AS INTEGER, b AS INTEGER, c AS INTEGER

are equivalent to

 
 DIM a%
 DIM b%
 DIM c%

and, as well, because the declaration of variables with forward propagation of variable type is allowed in BCX, the above declarations can be expressed as

 

 DIM AS INTEGER a, b, c 

Here are some other examples showing the declaration of variables with forward propagation of variable type.

Example:


 DIM AS DOUBLE A, B, C[2]                  ' all variables are doubles
 
 RAW AS INT A[] = {4,5}, B, C[2] = {0,1}   ' all variables are integer 
 
 DIM AS CHAR PTR PTR A, B[3]               ' all variables are POINTERS to POINTERS of CHAR

BCX also allows dimensioning different data type variables with one statement.

Example:

 
 DIM A%, B!, D$ * 1000, E[10,10]

creates an integer, a single, a string, and a 2 dimensional integer array.

Storage Class Specifiers

BCX recognizes the AUTO, REGISTER, EXTERN and STATIC storage class specifiers. Variables declared with the AUTO or REGISTER specifier have local persistence, that is, the values in those variables are lost when the subroutine or function in which they were declared is exited. Variables declared with the EXTERN or STATIC specifier have global persistence that is, the values in those variables are retained when the subroutine or function in which they were declared is exited.

AUTO storage class specifier Do not use AUTO if compiling with C++. Since 2010, the AUTO keyword is no longer a C++ storage-class specifier and has been repurposed.

When AUTO is used, an automatic variable, a variable with a local lifetime, is declared. The scope of an AUTO variable is limited to the block in which it was declared. AUTO is the default storage class for local variables but must be explicitly specified when programming threads.


 Syntax:
 
 AUTO AutoVar AS data type

 Parameters:

  • AutoVar name of variable
  • data type any valid data type.

EXTERN storage class specifier

When EXTERN is used, the variable declared with EXTERN becomes a reference to a variable with the same name defined externally in any source files of the program. The EXTERN declaration makes the external-level variable definition visible within the block. A variable declared with the EXTERN keyword is visible only in the block in which it is declared unless the external variable has been declared as a global.


 Syntax:
 
 EXTERN ExtVar AS data type

 Parameters:

  • ExtVar name of variable, same as externally defined name.
  • data type The type of name must match the type originally used to declare it in the external module.

STATIC storage class specifier

When STATIC is used within a SUB or FUNCTION, to dimension a variable, the variable will retain its value from call to call. When DIM or LOCAL is used within a SUB or FUNCTION to dimension a variable, the variable will not retain its value from call to call. STATIC variables are automatically initialized(set to zero value) only the first time they are declared.


 Syntax:
 
 STATIC StatVar AS data type

 Parameters:

  • StatVar name of variable.
  • data type any valid data type.

An example showing the STATIC difference:

 
 DIM add1more%, i%, int1%

 add1more% = 1

 FOR i% = 1 TO 5
  int1% = Count%(add1more%)
  PRINT "Total is "; int1%
 NEXT i%

 FUNCTION Count%(it%)
  STATIC total%
  total% = total% + it%
  FUNCTION = total%
 END FUNCTION

Result:


 Total is  1
 Total is  2
 Total is  3
 Total is  4
 Total is  5

Here is the same example without STATIC

 
 DIM add1more%, i%, int1%

 add1more% = 1

 FOR i% = 1 TO 5
  int1% = Count%(add1more%)
  PRINT "Total is "; int1%
 NEXT i%

 FUNCTION Count%(it%)
  DIM total%
  total% = total% + it%
  FUNCTION = total%
 END Function

Result:


 Total is  1
 Total is  1
 Total is  1
 Total is  1
 Total is  1

REGISTER storage class specifier Do not use REGISTER if compiling with C++. It was deprecated in the C++11 standard and removed from C++17 standard.

REGISTER is used to define local variables to be stored in a register instead of RAM. A REGISTER variable has a maximum size equal to the register size. The unary address-of operator(&) can not be applied to a REGISTER variable nor can the REGISTER keyword be used on arrays. REGISTER is best used with variables that need quick access. Note well that specifying REGISTER does not mean that the variable will be stored for certain in a register.


 Syntax:
 
 REGISTER RegVar AS data type

 Parameters:

  • RegVar name of variable.
  • data type The data type must be either an INTEGER type or a pointer.

VOLATILE data type qualifier

The VOLATILE data type qualifier specifies that the memory access to the variable, array or other data object is to be consistent. VOLATILE data can have its value changed without the control or detection of the compiler, for example, the system clock or other program updating a variable.

Here are some examples of data declarations with the VOLATILE data type qualifier.

 TYPE z
   DIM VOLATILE a AS INTEGER
   DIM c[20] AS CHAR
 END TYPE
 
 DIM VOLATILE ddd
 GLOBAL VOLATILE aaa AS INTEGER
 SHARED VOLATILE BBB AS z
 EXTERN VOLATILE ccc AS INTEGER
 
 SUB v()
   STATIC VOLATILE zz AS z
 
 END SUB

Variable Scope

BEGINBLOCK ... ENDBLOCK statements

Purpose: The BEGINBLOCK ... ENDBLOCK statements allow BCX to limit the scope of allocated varibles. A BEGINBLOCK statement is placed before the section to which you want to limit the scope and an ENDBLOCK statement specifies the end of the scope limited section.


 Syntax:

 BEGINBLOCK
  Variables defined here will be limited in scope 
  to the section between the BEGINBLOCK ... ENDBLOCK statements.
 ENDBLOCK

Example:


 DIM sProg$
 
 sProg$ = "This Program will show two MSGBOXs with different results" & CRLF$
 sProg$ = sProg$  &  "DIM i" & CRLF$
 sProg$ = sProg$  &  "i = 2" & CRLF$
 sProg$ = sProg$  &  "BEGINBLOCK" & CRLF$
 sProg$ = sProg$  &  "DIM i" & CRLF$
 sProg$ = sProg$  &  "i = 5" & CRLF$
 sProg$ = sProg$  &  "MSGBOX " & ENC$("i =") & " & STR$(i)" & CRLF$
 sProg$ = sProg$  &  "ENDBLOCK" & CRLF$
 sProg$ = sProg$  &  "MSGBOX " & ENC$("i =") & " & STR$(i)" & CRLF$
 
 MSGBOX sProg$
 DIM i
 i = 2
 
 BEGINBLOCK
  DIM i
  i = 5
  MSGBOX "i =" & STR$(i)
 ENDBLOCK
 
 MSGBOX "i =" & STR$(i)
 
 

Creating Global Variables

Variables declared with DIM at the module level of the program are automatically given global scope. They can be used anywhere in your program.

Also, global variables can be created anywhere in your program using the GLOBAL or SHARED keywords.

All variable names with global scope must be unique, including variables created using DIM in the main portion of the program. That means, you cannot have one global variable named A$ and another named A%. However, you could have one global variable named A$ and another global variable named a$ because BCX variables are case sensitive. Therefore A$ and a$ are seen as different variables.

Warning ! If a variable has global scope, the name of that variable must not be used as the name of any parameter in a FUNCTION or SUB statement. Below is an example of the problem that occurs when a globally scoped variable name is used also as the name of a FUNCTION parameter.

 
 GLOBAL a%
 a% = 12345

 CALL DoNot(a%)

 END PROGRAM

 SUB DoNot(a%) ' The a% parameter causes the variable
  a% = 987654 ' to be implicitly declared as a LOCAL
  DoIt()
 END SUB

 FUNCTION DoIt()
  PRINT "The number is ", a%
  FUNCTION = 0
 END FUNCTION

The program above will print "The number is 12345" because the value 987654 is assigned only to the SUB DoNot parameter a% which is implicitly created with a local scope. The globally scoped a% is never assigned the value 987654.

Dimensioning Variables in Subroutines and Functions

When DIM or LOCAL is used within a SUB or FUNCTION to dimension a variable, the variable is local in scope to that SUB or FUNCTION, or in other words, the variable is unknown to the rest of the program.

A variable dimensioned with DIM or LOCAL in a subroutine or function retains the value on exit, but will lose it on re-entry due to the automatic initialization. This point is important when returning pointers, as demonstrated in the following example.

 
 GLOBAL B AS CHAR PTR
 
 B = foo("Hello")
 'B$ is now using the storage provided by 
 'LOCAL A$ in function foo 
 B$ = B$ + " World"
 PRINT B$
 
 PRINT foo$("Second call to foo")
 PRINT B$
 
 END
 
 FUNCTION foo(text$) AS LPSTR
 LOCAL A$
 ' If this were changed to RAW A$ you would get a compile 
 ' warning and unpredictable results.   
 A$ = "(" + text$ + ")"
 FUNCTION = A
 END FUNCTION

Result:

(Hello) World
(Second CALL TO foo)
(Second CALL TO foo)

Example:

 
 DIM A!            ' Global
 DIM B!            ' Global
 DIM C!            ' Global
 
 C! = 100.123      '<<< This Value Should Not Change!
 B! = 123          '<<< This Value Should Not Change!
 A! = Fun!(B!, C!) '<<< "C" allows type translation automatically!
 
 PRINT "The Value Of A! = ", A!
 PRINT "The Value Of B! Should Still Be <123> ..." , B!
 
 FUNCTION Fun!(Y%, Z%)
   DIM A$
   DIM B!
   A$ = "Hello from inside our function!"     ' a local string variable
   PRINT A$
   'The B! Variable Below Is The Local
   'The C! Variable Is The Global Variable
   'The Z% Variable Is The Function Parameter Variable
   B! = 3 * Z% + C! + Y%
   FUNCTION  = B!
 END FUNCTION

Variables declared within a SUB or FUNCTION using a DIM or LOCAL statement are automatically initialized, that is, set to ASCII zero value, every time the function or subroutine procedure is called. For examples, see S52.bas   S56.bas   S61.bas.

Here's an example that creates a global variable total% in the FUNCTION Count%.

 
 DIM add1more%, i%, int1%

 add1more% = 1

 FOR i% = 1 TO 5
  int1% = Count%(add1more%)
  PRINT "Total is "; int1%
 NEXT i%

 FUNCTION Count%(it%)
  GLOBAL total%
  total% = total% + it%
  FUNCTION = total%
 END Function

Result:

 
 Total is  1
 Total is  2
 Total is  3
 Total is  4
 Total is  5

Dimensioning Dynamic Strings

BCX provides dynamically sized, one dimensional strings.

Dynamically dimensioned strings are limited only by available memory. A string dimensioned as

  
 DIM A$ * 5000000 

would allocate five megabytes for the string variable A$.

BCX uses ASCIIZ strings which are terminated with an ASCII NULL terminator character. A string must be dimensioned large enough to include this termninator.

It is important to remember, when dimensioning a dynamic string, that BCX uses ASCIIZ strings which are terminated with a single byte ASCII NULL terminator character to mark the end of the string. All BCX strings must be dimensioned to a size large enough to include this terminator. For example, if a string contains 15 characters then it must be dimensioned to at least 16 bytes.

Do not dimension dynamic strings outside of a SUB or FUNCTION when using $NOMAIN.

FREE statement

It is possible to create huge(multi-megabyte) string variables using the DIM, GLOBAL, SHARED, and LOCAL keywords. After this space is through being used, you must release the memory back to Windows for re-use by using the FREE keyword to guard against memory leaks which can adversely affect system performance, and in worse cases cause a crash due to an out of memory condition.

Here is a complete example:

 
 
 DIM Buffer$ * (1000 * LEN("Line No. ") + 1000 * 10)
 DIM A AS INTEGER

 FOR A = 1 TO 1000
  Buffer$ = Buffer$ & "Line No. " & _
  STR$(A) & CHR$(13) & CHR$(10)
 NEXT

 PRINT Buffer$
 A = LEN(Buffer$)
 PRINT "The length of Buffer$ =" , A , " bytes."
 FREE Buffer$        'release memory back to Windows

Remarks: All allocated memory is returned to Windows when your program ends.

Note well ! In GUI programs, when dimensioning a dynamic string, the DIM, LOCAL or GLOBAL statement MUST appear inside a BEGIN EVENTS ... END EVENTS structure or inside a FUNCTION or a SUB.

Although it is perfectly legal to dimension GLOBAL dynamic strings within a FUNCTION or SUB procedure, it is best if the string is dimensioned in the initialization section of the program and REDIM then is used to modify the size of the string in the procedure. Be sure to FREE the memory allocated for the string when it is no longer needed.

 
 GLOBAL Z$ * 1000             ' Z$ is global

 SUB FOO
  DIM    a$ * 1000             ' a$ is LOCAL with automatic Free
  LOCAL  b$ * 1000             ' b$ is LOCAL with automatic Free
  GLOBAL c$ * 1000, d$ * 1000  ' c$ and d$ are global
  DoSomeThing()
  FREE c$                      ' GLOBAL MUST be freed before exit
  FREE d$                      ' GLOBAL MUST be freed before exit
 END SUB

Dimensioning Dynamic Strings outside of a SUB or FUNCTION (Console mode only)

Do not dimension dynamic strings outside of a SUB or FUNCTION when using $NOMAIN.

Creating a dynamic variable using DIM or GLOBAL outside a SUB or FUNCTION will create a global dynamic string and it is up to the programmer to determine at what point in the program the variable must be freed.

Dynamic strings outside a SUB or FUNCTION procedure can be dimensioned with the following syntax:

 
 Syntax: DIM A3$ * 2048
  Purpose:  Allocates space for a 2048 byte global dynamic string.

 Syntax: GLOBAL Buffar$ * lenbuf%
  Purpose:  Allocates space for a global dynamic string the size of lenbuf%

Dimensioning Dynamic Strings inside a SUB or FUNCTION

When used inside a subroutine or function, BCX takes care of the string memory de-allocation code. This is important to help keep memory leaks out of your programs.

You can use dynamic strings inside a SUB or FUNCTION using the following syntax:

 
 Syntax: DIM A3$ * 2048
  Purpose:  Allocates space for a 2048 byte local dynamic string.

 Syntax: DIM LOCAL A4$ * 1
  Purpose:  Allocates space for a 1 byte local dynamic string

 Syntax: LOCAL A5$ * 1024
  Purpose:  Allocates space for a 1024 byte local dynamic string

 Syntax: GLOBAL Buffar$ * lenbuf%
  Purpose:  Allocates space for a global dynamic string the size of lenbuf%

BCX uses ASCIIZ strings which are terminated with an ASCII NULL terminator character. A string must be dimensioned large enough to include this terminator.

If located in a function or subroutine, using either DIM or LOCAL will create a variable local in scope. Locally dimensioned dynamic strings must exist on the base level of the SUB or FUNCTION. They must not be dimensioned inside any IF...ENDIF, FOR...NEXT, SELECT...END SELECT, or LOOP structures. The most appropriate place for these statements is immediately after the SUB or FUNCTION declaration. BCX, in most instances, will automatically free the memory allocated. One exception to this is that DYNAMIC variables/arrays not automatically freed when they are declared within a FUNCTION MAIN() under a $NOMAIN directive.

Global dynamically dimensioned strings must be freed after use.

Freeing strings is very important if they are in a loop or in a procedure that may be called several times. If the variable is not freed before it is dimensioned again, a "memory leak" occurs with a new chunk of memory allocated in which to store the string each time the dynamic variable is dimensioned. Unless FREEd, the last chunk is not deallocated so it is not available for use. If this happens in an often repeated loop, the memory can be used up to the point of causing the machine to crash. Finding the appropriate point to free a variable is not simple and requires a thorough understanding of how the program is structured.

The RAW or AUTO LOCAL statement

Purpose: RAW or its alias AUTO LOCAL can be used to create uninitialized variables. RAW does not clear the memory block of the created variable by filling it with zeros. A RAW variable created in a subroutine or function is not STATIC in retaining a value between calls to the procedure.


 Syntax 1:
 
 RAW VariableName

 Parameters:

  • VariableName The name of the variable being dimensioned.

Remarks:

RAW does not initialize variables inside SUB or FUNCTION procedures, for example,

 
 SUB RawSub1()
  RAW str1$
 END SUB

translates to C source code

 
 void RawSub1 ()
 {
   char    str1[cSizeOfDefaultString];
 }

while

 
 SUB RawSub1()
  DIM str1$
 END SUB

translates to C source code

 
void RawSub1 ()
{
  char    str1[cSizeOfDefaultString]={0};
}

RAW used outside of a SUB or FUNCTION procedure is the same as STATIC, for example,

 
 RAW a$

translates to C source code

 
 static char a [cSizeOfDefaultString];

LOCAL variables slow things down a bit because they need to be zero'd out each time. RAW are the fastest but require that you give them meaningful values as needed. Consider this ... why take the time to zero an integer if you unconditionally assign it a value.

Dimensioning Arrays

 
 Syntax: DIM C% [100,100]
  Purpose:  Allocates a two dimensional array of integers

 Syntax: DIM A6$ [10]
  Purpose:  Allocates an array of 10 2048 byte strings

 Syntax: DIM A7$ [10,1024] AS CHAR
  Purpose:  Allocates an array of 10 1024 byte strings
  Remarks: To dimension an array of strings in a function
 or subroutine the AS CHAR qualifier MUST be appended.

 Syntax: DIM A8$ [10,1024]
  Purpose:  Allocates a two dimensional array
           of 10 by 1024 2048 byte strings

Initialization of Arrays

The elements of an array can be initialized, that is, given a value, at the time of definition by using a brace-enclosed list of comma-separated constant expressions. The one-dimensional array definition in Example 1 is a completely initialized demonstration of this technique.

Example 1:


 DIM TheArray%[3] = { 2, 4, 8 }
  
 PRINT "TheArray%[0] =", TheArray%[0]
 PRINT "TheArray%[1] =", TheArray%[1]
 PRINT "TheArray%[2] =", TheArray%[2]

Result:

 
TheArray%[0] = 2
TheArray%[1] = 4
TheArray%[2] = 8

Note well that there is a length limitation of 128 bytes for the total length of the initializers list between the first brace "{" and the final brace "}". For arrays containing larger sets of elements, use the SET ... END SET statements.

Example 2: shows a partially initialized one dimensional array.


 DIM TheArray%[3] = { 2, 4}
  
 PRINT "TheArray%[0] =", TheArray%[0]
 PRINT "TheArray%[1] =", TheArray%[1]
 PRINT "TheArray%[2] =", TheArray%[2]

Result:

 
TheArray%[0] = 2
TheArray%[1] = 4
TheArray%[2] = 0

Example 3: shows how to specify which elements of an array are to be initialized.


 DIM TheArray%[3] = { [0] = 2, [2] = 8 }
  
 PRINT "TheArray%[0] =", TheArray%[0]
 PRINT "TheArray%[1] =", TheArray%[1]
 PRINT "TheArray%[2] =", TheArray%[2]

Result:

 
TheArray%[0] = 2
TheArray%[1] = 0
TheArray%[2] = 8

Example 4: shows how to initialize elements of an array in which the index size is not specified.


 DIM TheArray%[ ] = { 2, 4, 8 }
  
 PRINT "TheArray%[0] =", TheArray%[0]
 PRINT "TheArray%[1] =", TheArray%[1]
 PRINT "TheArray%[2] =", TheArray%[2]

Because no index size was specified for array2%, three initialized elements are defined by the compiler.

Result:

 
TheArray%[0] = 2
TheArray%[1] = 4
TheArray%[2] = 8

Example 5: shows how to use variables as elements in an array in which both the array and the element variables have global scope. The example also shows how to access the values efficiently using the CRT function "memmove", which is wrapped into a macro.


 DIM Ind0
 DIM Ind1
 DIM Ind2
  
 DIM RAW TheArray[3]= {&Ind0, &Ind1, &Ind2} AS LPVOID
  
 Ind0 = 1
 Ind1 = 2
 Ind2 = 3
  
 CALL Ordinate()
  
 SUB Ordinate()
   DIM i AS INTEGER
  
   STORE (i,TheArray[0]) : PRINT i
   STORE (i,TheArray[1]) : PRINT i
   STORE (i,TheArray[2]) : PRINT i
 END SUB
  
 CONST STORE(src,des) memmove(&src,des,SIZEOF(src))

Result:

 
 1
 2
 3

OPTION BASE directive

The default lower bound for an index in a user defined array in a BCX program, normally 0, can be set to another value using the OPTION BASE directive. Note well that arrays defined in the runtime functions such as SPLIT and DSPLIT will not use the value set by OPTION BASE but will use the BCX default lower bound of 0.


 Syntax:
 
 OPTION BASE Number%

 Parameters:

  • Number% can be any value but most commonly will be 0 or 1.

Here are a few comments and a small sample explaining how BCX treats this directive.

OPTION BASE works with static and dynamic arrays. Whenever BCX detects an OPTION BASE directive, BCX sets a global integer variable named "OptionBase" to the size of the OPTION BASE . This process can be seen in the following snippet from the BCX translator:

 
 IF L_Stk_1$ = "option" AND L_Stk_2$ = "base" THEN
  OptionBase = VAL(Stk$[3])
  Ndx = 0
  EXIT SUB
 END IF

Later on in the translation process, this code takes over:

 
 IF OptionBase THEN
  IF Stk$[i] = "[" THEN Stk$[i] = "[" & LTRIM$(STR$(OptionBase)) & "+"
 END IF

This means that you can use numerous OPTION BASE directives in your BCX source code. The thing to remember is that the current OPTION BASE value is a function of its location(line number) in your BASIC source code. Also remember that BCX reads source code in a linear manner, including BASIC source files that are merged into your main BASIC source code file using the $INCLUDE directive.

Here is a GUI example using OPTION BASE

 
 OPTION BASE 20
 GLOBAL MyStrings$[10]   ' Translated >>> MyStrings[20+10][2048]

 GUI "OpBase"

 SUB FORMLOAD
  DIM F AS CONTROL
  F = BCX_FORM("Option Base")
  CENTER(F)
  SHOW(F)
  OPTION BASE 1
  DIM a[10]  ' Translated >>> a[1+10]
 END SUB

 OPTION BASE 0

 SUB FOO
  DIM b[10]   ' Translated >>> b[10]
 END SUB

 SUB MOO

  OPTION BASE 5

  DIM c[10]  ' Translated >>> c[5+10]
 END SUB

 BEGIN EVENTS
 END EVENTS

For another example of using OPTION BASE see S145.bas.

Important notes about Dimensioning Arrays

Similar to C language, BCX arrays can use multiple square brackets to enclose the individual dimension values. For example, in BCX, DIM A%[3][5] would indicate a two dimensional array of integers.

Also, in BCX, if an array is dimensioned as DIM Array$[30] , there will be 30 storage locations for data with the index numbered from Array$[0] to Array$[29]. This differs from QBASIC which will allocate 31 storage locationsfor data with the index numbered from Array$[0] to Array$[30]..

Dimensioning DYNAMIC Arrays

DYNAMIC arrays can be any data type and may be global or local. DYNAMIC arrays differ from static arrays in that DYNAMIC arrays can be redimensioned by using REDIM.

Note well ! In GUI programs, when dimensioning a DYNAMIC array, the
DIM DYNAMIC
LOCAL DYNAMIC,
or
GLOBAL DYNAMIC
statement must appear inside a BEGIN EVENTS ... END EVENTS structure or inside a FUNCTION or a SUB.

If a GLOBAL DYNAMIC array is to be used inside a FUNCTION or a SUB, it is best if the string is dimensioned in the initialization section of the program and REDIM then is used to modify the size of the string in the procedure. Be sure to FREE the memory allocated for the array when it is no longer needed.

Also, please note that DYNAMIC arrays are not automatically freed when they are declared within a FUNCTION MAIN() under a $NOMAIN directive.

 
 Syntax: DIM DYNAMIC A[10,10]
  Purpose:  Allocates a two dimensional array of integers

 Syntax: DIM DYNAMIC B![10,10]
  Purpose:  Allocates a two dimensional array of single floating point numbers

 Syntax: DIM DYNAMIC C#[10,10]
  Purpose:  Allocates a two dimensional array of double floating point numbers

 Syntax: DIM DYNAMIC D$[10,10]
  Purpose:  Allocates a two dimensional 10 by 10 array of 2048 byte strings

 Syntax: DIM DYNAMIC E[10,10] AS CHAR
  Purpose:  Allocates an single dimensioned array of 10 10 byte strings

The default string length of each element in a DYNAMIC string array is 2048 bytes, consistent with the rest of BCX. However, the default can be overridden by adding the AS CHAR data type qualifier. This causes the second parameter to define the string length of each element, similiar to declaring static string arrays:

 
 Default:      DIM DYNAMIC A$[1000] ' 2048 bytes per cell
 User defined: DIM DYNAMIC A$[1000,80] AS CHAR ' 80 bytes per cell

LOCAL DYNAMIC arrays in most instances are automatically freed. However, one exception to this is that DYNAMIC arrays are not automatically freed when they are declared within a FUNCTION MAIN() under a $NOMAIN directive.

GLOBAL DYNAMIC arrays must be deallocated using

 
 FREE ArrayName

Here is a program that demonstrates redimensioning a DYNAMIC array.

 
 CLS

 OPTION BASE 1

 DIM DYNAMIC Buffer$[7,5] AS CHAR 'Seven cells, 5 bytes each

 FOR INTEGER i = 1 TO 7
   Buffer$[i] = "No" & STR$(i)
   PRINT Buffer$[i]
 NEXT

 ? "******************"
 ? "Redimensioning ..."
 ? "******************"

 REDIM Buffer$[20,10] AS CHAR 'twenty cells, 10 bytes each

 FOR INT i = 10 TO 20
  Buffer$[i] = "No" & STR$(i)
  PRINT Buffer$[i]
 NEXT
 
 FREE Buffer

 KEYPRESS

The default lower bound for all array indexes in the program can be set using the OPTION BASE statement. A complete explanation for using OPTION BASE is above in the OPTION BASE section.

Here is an example using a DYNAMIC variable length array.


 OPTION BASE 1
 
 DIM DYNAMIC A$[100]        'Dynamic string arrays default to OPTION BASE 1
 
 FOR INTEGER I = 1 TO 100
   A$[I] = "A$[] ... THIS IS LINE " & STR$(I)
   PRINT A$[I]
 NEXT
 
 FREE A$  ' Release memory back to the operating system
 FREE A$  ' An intentional error -- BCX handles it automatically
 
 CALL FOO_TEST
 
 SUB FOO_TEST()
   DIM RAW E = 100
   DIM DYNAMIC A$[E]
   DIM DYNAMIC B$[E]
   DIM DYNAMIC C$[E]
   DIM DYNAMIC D$[E]
 
   PRINT "Storing Items In A$[]"
   FOR INTEGER I = 1 TO E
     A$[I] = "A$[] ... THIS IS LINE " & STR$(I)
   NEXT
 
   PRINT "Storing Items In B$[]"
   FOR INTEGER I = 1 TO E
     B$[I] = "B$[] ... THIS IS LINE " & STR$(I)
   NEXT
 
   PRINT "Storing Items In C$[]"
   FOR INTEGER I = 1 TO E
     C$[I] = "C$[] ... THIS IS LINE " & STR$(I)
   NEXT
 
   PRINT "Storing Items In D$[]"
   FOR INTEGER I = 1 TO E
     D$[I] = "D$[] ... THIS IS LINE " & STR$(I)
   NEXT
 
   FOR INTEGER I = 1 TO E
     PRINT A$[I]
     PRINT B$[I]
     PRINT C$[I]
     PRINT D$[I]
   NEXT
 
 END SUB

Warning ! When dimensioning a DYNAMIC variable length array, do not append any data type declaration suffix, that is, %, to an array index variable.

 
 DIM DYNAMIC A$[E]

is legal, but

 
 DIM DYNAMIC A$[E%]

is not legal and will cause compiler errors.

Warning ! When dimensioning a DYNAMIC variable length array, using a floating point variable as an index in an array will result in undefined behavior.

ISPTR macro

ISPTR is a macro that simply says, if this is a valid element belonging to a dynamic string array, return it, otherwise return zero. This eliminates the need to know how many elements are being passed to a SUB or FUNCTION.

STRARRAY data type-declaration

STRARRAY, instructs BCX to generate code specifying that a dynamic string array is being passed to a user defined SUB or FUNCTION.

 
 DIM DYNAMIC Buf$ [10]

 Buf$[0] = "Zero"
 Buf$[1] = "One"
 Buf$[2] = "Two"
 Buf$[5] = "Five"

 CALL Foo(Buf$)

 SUB Foo(A$ AS STRARRAY)
  LOCAL i
  WHILE ISPTR(A$[i])
    IF A$[i] > "" THEN PRINT A$[i]
     INCR i
  WEND
 END SUB

Result:

 
 Zero
 One
 Two
 Five

Using PTR to create pointer variables

Pointer variables can be created using the reserved keyword PTR.

Any of the integer, floating point or string data types listed above can be used with AS PTR appended to create a pointer variable of that data type. Here are two examples:

 
 Syntax: DIM LOCAL a AS INTEGER PTR
  Purpose:  Allocates space for a pointer to integer

 Syntax: DIM STATIC a AS SINGLE PTR
  Purpose:  Allocates space for a pointer to single floating point number

PTR also can be used in SUB and FUNCTION parameter lists, for example,

  
 DIM rct AS RECT
 
  rct.left = 1
  rct.top = 2
  rct.right = 100
  rct.bottom = 100
 
  rectProc(&rct) ' The argument being passed by reference
                 ' must be preceded by an ampersand.
  getchar()
 
  SUB rectProc(rct AS RECT PTR)
   ? rct->left
   ? rct->top
   ? rct->right
   ? rct->bottom
  END SUB

Using PTR PTR to create pointers to pointer variables

Pointers to pointer variables can be created using the reserved keyword PTR PTR. Any of the integer, floating point or string data types listed above can be used with AS PTR PTR appended to create a a pointer to a pointer variable of that data type. Here are two examples:

 
 Syntax: DIM LOCAL a AS INTEGER PTR PTR
  Purpose:  Allocates space for a pointer to a pointer to integer

 Syntax: DIM STATIC a AS SINGLE PTR PTR
  Purpose:  Allocates space for a pointer to a pointer to single floating point number

PTR PTR also can be used in SUB and FUNCTION parameter lists, for example,

  
 DIM str1$
 
 str1$ = "Hello worlds"
 
 DIM pstr1 AS CHAR PTR
 
 pstr1 = str1$
 
 Increment(&pstr1) 
 
 PRINT pstr1$
 
 SUB Increment(ppstr1 AS CHAR PTR PTR)
  ++*ppstr1    
 END SUB

REDIM statement

Purpose: Dynamically dimensioned arrays and string variables can be cleared and redimensioned, increasing or decreasing an array's size, using the REDIM statement.


 Syntax:
 
 REDIM ArrayTypeX[Index]

 Parameters:

  • ArrayTypeX Name of the array with an appended data type specifier.
  • Index can be can be any of a literal number, a scalar variable, an array or a function.

Remarks:

When REDIM is used, the values in the array or string variable are not preserved because a new array is created.

Note well that although the number of elements in a dimension can be altered, the number of dimensions can not be changed, for example, a two dimensional array can not be changed to a three dimensional array with REDIM.

Also, here is a warning to remember that when REDIM is used to redimension a global variable in a function or subroutine, the initial dimensioning code must physically precede the code where the REDIM is used. For example,

This is valid

 
 SUB YaGood1()
  GLOBAL DYNAMIC Buffer$[100]
 END SUB

 SUB YaGood2
  REDIM Buffer$[200]
 END SUB

while this is not valid.

 
 SUB NoGood1()
  REDIM Buffer$[200]
 END SUB

 SUB NoGood2
  GLOBAL DYNAMIC Buffer$[100]
 END SUB

Here are two console mode samples.

The first example redimensions a dynamic string.

 
 DIM A$ * 14
 A$ = "Hello, World!"
 PRINT A$

 REDIM A$ * 1001
 A$ = REPEAT$(1000,"A")
 PRINT A$
 FREE A$

This example redimensions a single dimension array.

 
 DIM DYNAMIC A$[10]
 REDIM       A$[20]
 A$[19] = "Hello"
 PRINT A$[19]

REDIM PRESERVE statement

Dynamically dimensioned variables and arrays can be redimensioned, increasing or decreasing the size of a dimension, with the contents of the object unchanged using the REDIM PRESERVE statement.


 Syntax 1:
 
 REDIM PRESERVE DynaString$ * Length%

 Parameters:

  • StrArray$ Name of a dynamic string to be redimensioned.
  • Length%is the length of the string to be redimensioned. This argument can be can be any of a literal number, a scalar variable, an array or a function.

 Syntax 2:
 
 REDIM PRESERVE ArrayX[Index]

 Parameters:

  • ArrayX is the name of an array to be redimensioned. The data type specifier for the array must be appended to the name or specified by using the AS data type syntax.
  • Index can be can be any of a literal number, a scalar variable, an array or a function.

 Syntax 3:
 
 REDIM PRESERVE Array[Index1, Index2, Index3, Index4] AS data type

 Parameters:

  • Array is the name of an array to be redimensioned. The data type specifier for the array must be appended to the name or specified by using the AS data type syntax.
  • IndexX can be any of a literal number, a scalar variable, an array or a function.

Remarks:

When REDIM PRESERVE is used, the values in the string or array variable are preserved up to the lesser of the new and old sizes.

Note well that although the size of the dimensions can be altered, the number of dimensions can not be changed, for example, a two dimensional array can not be changed to a three dimensional array with REDIM PRESERVE.

REDIM PRESERVE supports all data types in both single and multiple dimension arrays.

Example 1: shows that data is preserved after REDIM PRESERVE has been applied to an array.

 
 TYPE foo
   A$
   B$
 END TYPE
 
 DIM DYNAMIC myfoo[3] AS foo
 
 myfoo[2].A$ = "This is our initial data"
 myfoo[2].B$ = "prior to REDIM and is preserved."
 
 REDIM PRESERVE myfoo[9]
 
 myfoo[8].A$ = "This data has been added "
 myfoo[8].B$ = "after REDIM has been applied to the array."
 
 PRINT myfoo[2].A$
 PRINT myfoo[2].B$
 PRINT
 PRINT myfoo[8].A$
 PRINT myfoo[8].B$
 

Example 2: This example redimensions a dynamic string.

 
 DIM b$ * 11
 b$ = "1234567890"
 ? b$
 REDIM b$ * 15
 b$ = b$ & "ABCD"
 ? b$
 REDIM PRESERVE b$ * 21
 b$ = b$ & "EFGHIJKLMNOPQRST"
 ? b$

Example 3: This example redimensions a single dimension array.


 DIM DYNAMIC a$[4]
 
 a$[0] = "This"
 a$[1] = "is"
 a$[2] = "a"
 a$[3] = "test"
 
 ? "Initial values"
 ? "a$[0] = ", a$[0]
 ? "a$[1] = ", a$[1]
 ? "a$[2] = ", a$[2]
 ? "a$[3] = ", a$[3]
 
 REDIM PRESERVE a$[6]
 
 a$[4] = "that shows the above"
 a$[5] = "contents preserved."
 
 ? "After REDIM PRESERVE"
 ? "a$[0] = ", a$[0]
 ? "a$[1] = ", a$[1]
 ? "a$[2] = ", a$[2]
 ? "a$[3] = ", a$[3]
 ? "a$[4] = ", a$[4]
 ? "a$[5] = ", a$[5]
 
 REDIM a$[10]
 
 a$[6] = "the contents above"
 a$[7] = "are not preserved."
 
 ? "After REDIM"
 ? "a$[0] = ", a$[0]
 ? "a$[1] = ", a$[1]
 ? "a$[2] = ", a$[2]
 ? "a$[3] = ", a$[3]
 ? "a$[4] = ", a$[4]
 ? "a$[5] = ", a$[5]
 ? "a$[6] = ", a$[6]
 ? "a$[7] = ", a$[7]

Example 4: This example redimensions a multiple dimension array.

 
 GLOBAL DYNAMIC a[6,7,4,4] AS INTEGER

 a[0,0,0,0] = 4
 a[1,1,0,0] = 1
 a[2,2,1,0] = 3
 a[3,3,0,0] = 2

 ? a[0,0,0,0]
 ? a[1,1,0,0]
 ? a[2,2,1,0]
 ? a[3,3,0,0]

 REDIM PRESERVE a[7,7,4,4] AS INTEGER
 ? a[0,0,0,0]
 ? a[1,1,0,0]
 ? a[2,2,1,0]
 ? a[3,3,0,0]

 REDIM a[5,5,4,4] AS INTEGER
 ? a[0,0,0,0]
 ? a[1,1,0,0]
 ? a[2,2,1,0]
 ? a[3,3,0,0]

 getchar();

$GENFREE directive

To free all global variables place the $GENFREE directive at the beginning of the program then CALL FREEGLOBALS from the point at which the global variables are to be freed.

Here is a complete example.


 $GENFREE
 
 GLOBAL DYNAMIC aa$[100] AS CHAR
 DIM DYNAMIC bb$[100] AS CHAR
 aa$ = REPEAT$(20, "aa$") 
 bb$ = REPEAT$(20, "bb$") 
 
 PRINT "Before FREEGLOBALS"
 PRINT "aa$ = ", aa$
 PRINT "bb$ = ", bb$
 
 CALL x()
  
 CALL FREEGLOBALS
 
 PRINT " "
 PRINT "After FREEGLOBALS"
 PRINT "aa$ = ", aa$
 PRINT "bb$ = ", bb$
 PRINT "cc$ = ", dd$
 PRINT "dd$ = ", dd$ 
 
 SUB x()
   GLOBAL DYNAMIC cc$[100] AS CHAR
   GLOBAL dd$ * 100
   cc$ = REPEAT$(20, "cc$") 
   dd$ = REPEAT$(20, "dd$")
   PRINT "cc$ = ", cc$
   PRINT "dd$ = ", dd$   
 END SUB

$TYPEDEF directive


 $TYPEDEF <simple one line C/C++ typedef> 

Example:

 $TYPEDEF long(CALLBACK *CPP_FARPROC)(char *)

translates to:

 typedef long(CALLBACK *CPP_FARPROC)(char *);

User Defined Type (UDT)

BCX supports individual and arrays of User Defined Types.

It is important to remember, when dimensioning a string within a UDT, that BCX uses ASCIIZ strings which are terminated with a single byte ASCII NULL terminator character to mark the end of the string. All BCX strings must be dimensioned to a size large enough to include this terminator. For example, if a string contains 15 characters then it must be dimensioned to at least 16 bytes.

A SUB can be included in a UDT, as can a FUNCTION, however, OVERLOADED or OPTIONAL FUNCTION or SUB procedures are NOT allowed in a user defined type structure.

 
 TYPE FOO
  MyVar
  SUB Process(This AS FOO_CLASS)
  FUNCTION Calc(This AS FOO_CLASS, Arg AS DOUBLE) AS DOUBLE
 END TYPE

BCX automatically creates a structure pointer by prepending *LP to the name of your UDT.
For example, this BCX code,

 
 TYPE SPC
  self   AS VOID*
  parent AS VOID*
  child  AS VOID*
  daDaTa AS VOID*
  mtbl   AS VOID*
 END TYPE

produces this "C" structure

 
 typedef struct _SPC
 {
   VOID*  self;
   VOID*  parent;
   VOID*  child;
   VOID*  daDaTa;
   VOID*  mtbl;
 }SPC, *LPSPC;

Example 1:

 
 TYPE MYBOX
   Top%
   Left%
   Width%
   Height%
   Fill AS BOOL
 END TYPE
 
 DIM abc AS MYBOX
 
 abc.Top%     = 100
 abc.Left%    = 300
 abc.Width%   = 100
 abc.Height%  = 100
 abc.Fill     = TRUE
 
 PRINT abc.Top%
 PRINT abc.Left%
 PRINT abc.Width%
 PRINT abc.Height%
 PRINT abc.Fill

Example 2:

 
 TYPE MYREC
  B$ [2083] AS CHAR
 END TYPE

 DIM Test AS MYREC
 DIM A$ * 2083

 A$ = "test string"
 Test.B$ = "Next"

 PRINT A$
 PRINT Test.B$

Example 3: Here is a short example in which a user defined type is used to return multiple values from a FUNCTION.

 
 TYPE test
  a$[5] AS CHAR
  b$[5] AS CHAR
 END TYPE

 DIM x$

 DIM v AS test

 x$ = "ABC and XYZ"

 v = dfunc(x$)
 PRINT v.a$
 PRINT v.b$

 FUNCTION dfunc(d$) AS test
  LOCAL f AS test
  f.a$ = LEFT$(d$,3)
  f.b$ = RIGHT$(d$,3)
  FUNCTION = f
 END FUNCTION

Result:

 
 ABC
 XYZ

Example 4: Here is a more complex program showing off multi-dimensional user defined type.

 
 TYPE QWERTY
   DIM a
   DIM b!
   DIM c$[80] AS CHAR
   DIM q AS RECT
 END TYPE
 
 GLOBAL MyType [10,10,10] AS QWERTY
 
 MyType [2,3,4].a   =  1
 MyType [2,3,4].b!  =  2.345
 MyType [2,3,4].c$  = "hello world from a poly-dimensional udt!"
 
 PRINT         MyType[2,3,4].a
 PRINT         MyType[2,3,4].b!
 PRINT UCASE$(MyType[2,3,4].c$)

Example 5: Using the WITH ... END WITH control flow statement, the multi-dimensional user defined types Example 4 above can be written as follows.

 
 TYPE QWERTY
 DIM a
 DIM b!
 DIM c$ [80] AS CHAR
 DIM q AS RECT
 END TYPE

 GLOBAL MyType [10,10,10] AS QWERTY

 WITH MyType[2,3,4]
  .a = 1
  .b! = 2.345
  .c$ = "hello world from a poly-dimensional udt!"
  PRINT .a
  PRINT .b!
  PRINT UCASE$(.c$)
 END WITH
 

Example 6: Here is an example demonstrating dynamic memory allocation of the members in a user defined type.

 
 TYPE NODE_TYP
   id AS INTEGER 'element number
   name$[32] AS CHAR 'Storage area
   A1 AS CHAR PTR    'Storage area determined at run-time
   A2 AS CHAR PTR    'Storage area determined at run-time
   A3 AS CHAR PTR    'Storage area determined at run-time
   next_node AS NODE_TYP PTR
   previous_node AS NODE_TYP PTR
 END TYPE

 DIM F AS NODE_TYP
 DIM X AS NODE_TYP PTR
 
 CALL AllocateStringSpace(&F, 100, 1000, 10000)
 F.A1 = "this holds 99"
 F.A2 = "this holds 999"
 F.A3 = "this holds 9999"
 ? F.A1$
 ? F.A2$
 ? F.A3$

 F.next_node = AllocateNode()
 X = F.next_node
 CALL AllocateStringSpace(X, 1000, 2000, 80000)
 X->A1 = "this holds 999"
 X->A2 = "this holds 1999"
 X->A3 = "this holds 79999"
 ? X->A1$
 ? X->A2$
 ? X->A3$

 getchar()

 SUB AllocateStringSpace(Node AS NODE_TYP PTR, _
                             A1Length AS LONG, _
                             A2Length AS LONG, _
                             A3Length AS LONG)
  !Node->A1 = calloc(1,A1Length);
  !Node->A2 = calloc(1,A2Length);
  !Node->A3 = calloc(1,A3Length);
 END SUB

 FUNCTION AllocateNode() AS NODE_TYP PTR
  !return calloc(1,sizeof(NODE_TYP));
 END FUNCTION

Example 7: Like Example 6, this example also demonstrates dynamic memory allocation of the members in a user defined type. The syntax in this example is much simpler because of new code introduced in BCX version 5.12 to allow the use of the DYNAMIC type qualifier a with member of a user defined type.

 
 TYPE NODE_TYP
   id AS INTEGER 'element number
   name$[32] AS CHAR 'Storage area
   DYNAMIC A1$ 'Storage area determined at run-time
   DYNAMIC A2$ 'Storage area determined at run-time
   DYNAMIC A3$ 'Storage area determined at run-time
   DYNAMIC next_node[] AS NODE_TYP
   DYNAMIC prev_node[] AS NODE_TYP
 END TYPE
 
 DIM F AS NODE_TYP
 DIM X AS NODE_TYP PTR
 
 REDIM F.A1$ * 100
 REDIM F.A2$ * 1000
 REDIM F.A3$ * 10000
 
 F.A1$ = "this holds 99"
 F.A2$ = "this holds 999"
 F.A3$ = "this holds 9999"
 ? F.A1$
 ? F.A2$
 ? F.A3$
 
 REDIM F.next_node[1]
 X = &F.next_node[0]
 
 REDIM X->A1$ * 1000
 REDIM X->A2$ * 2000
 REDIM X->A3$ * 80000
 
 X->A1$ = "this holds 999" 
 X->A2$ = "this holds 1999"
 X->A3$ = "this holds 79999" 
 
 ? X->A1$
 ? X->A2$
 ? X->A3$
 
 getchar()
 

Example 8: How to REDIM a dynamic array inside a user defined type.

 
 TYPE First
  a$
  b%
 END TYPE

 TYPE Second
  c%
  DIM Something AS First PTR
 END TYPE

 DIM i
 
 DIM DYNAMIC Third[0] AS Second 'need an array size [0] will do
 
 REDIM Third[6]
 
 Third[0].Something = calloc(10,sizeof(First))
 FOR i = 0 TO 9
  Third[0].Something[i].b% = i
  Third[0].Something[i].a$ = "this" + STR$(i)
 NEXT
 FOR i = 0 TO 9
  ? Third[0].Something[i].b%
  ? Third[0].Something[i].a$
 NEXT
 
 getchar()

Example 9: Like Example 8, this example also demonstrates how to REDIM a dynamic array inside a user defined type. The syntax in this example is much simpler because of new code introduced in BCX version 5.12 to allow the use of the DYNAMIC type qualifier a with member of a user defined type.

 
 TYPE First
   a$
   b%
 END TYPE
 
 TYPE Second
   c%
   DYNAMIC Something[] AS First 
 END TYPE
 
 DIM DYNAMIC Third[6] AS Second 
 DIM i, ii
 
 FOR ii = 0 TO 5
   REDIM Third[ii].Something[10]
   FOR i = 0 TO 9
     Third[ii].Something[i].b% = i
     Third[ii].Something[i].a$ = "this is Something" + _
                                            STR$(i) + _
                                         " of Third" + _
                                            STR$(ii)
   NEXT
 NEXT
 
 FOR ii = 0 TO 5
   FOR i = 0 TO 9
     ? Third[ii].Something[i].b%
     ? Third[ii].Something[i].a$
   NEXT
 NEXT
 
 FOR ii = 0 TO 5
   FREE Third[ii].Something
 NEXT
 
 FREE Third
 
 getchar()

UNION ... END UNION statement

A UNION is similiar to a User Defined Type(UDT) but a UNION can hold the value of only one of its members at any one time but the active member can be changed at runtime and the UNION will then hold the value of the active member. The total size of a UNION is the size of the data type of its largest member.

In the sample below, you might think that the size of the UNION Foo would be 4 + 2048 + 4, counting the integer, the string and the single members of the UNION Foo, but that is not the case. The size of the UNION Foo will be the size of the largest member, that is, the string b$, which has a size of 2048 bytes.

 
 UNION Foo
   a  AS INTEGER
   b$
   c  AS SINGLE
 END UNION
 
 DIM Bloof AS Foo
  
 Bloof.a  = 1               
 PRINT Bloof.a
 
 Bloof.b$ = "Hello, World!" 
 PRINT Bloof.b$
 
 PRINT Bloof.a
 
 Bloof.c! = 3.14159         
 PRINT Bloof.c!

Result:

 1
Hello, World!
 1819043144
 3.14159

The result above shows that a UNION can hold the value of only one of its members at any one time. After Bloof.b$ has been been made the active member of the UNION, and assigned a string "Hello World", the original value of 1 assigned to the UNION member Bloof.a is no longer valid. The value, 1819043144, that PRINT Bloof.a then produces is a 32 bit integer representing, in little endian order, the first 4 bytes of the string "Hello World".

 1819043144 = 0x6C6C6548 Hex

 6C = ASCII l
 6C = ASCII l
 65 = ASCII e
 48 = ASCII H

Here's another example. If you create a union like this:

 
 UNION Blurf
  A$
  B$
  C$
 END UNION

 DIM Quarf AS Blurf

you might think that the size of Quarf would be 3 x 2048 bytes but, in fact, it is only 1 x 2048 bytes, since a UNION can hold only one value at a time.

A UNION can hold any type of data, even other UNION or user defined types.

Example: TYPE and UNION can be nested as in the following program.

 
 TYPE BE_CONFIG
   dwConfig AS DWORD
   UNION format
     TYPE mp3
       dwSampleRate AS DWORD
       byMode AS BYTE
       wBitrate AS WORD
       bPrivate AS BOOL
       bCRC AS BOOL
       bCopyright AS BOOL
       bOriginal AS BOOL
     END TYPE
     TYPE aac
       dwSampleRate AS DWORD
       byMode AS BYTE
       wBitrate AS WORD
       byEncodingMethod AS BYTE
     END TYPE
   END UNION
 END TYPE
 
 DIM T AS BE_CONFIG
 
 T.format.mp3.byMode = 1
 T.format.mp3.bPrivate = 2
  
 T.format.aac.byEncodingMethod = 3
 T.format.aac.dwSampleRate = 44100
 
 PRINT T.format.mp3.byMode
 PRINT T.format.mp3.bPrivate
  
 PRINT T.format.aac.byEncodingMethod
 PRINT T.format.aac.dwSampleRate