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.
%
in DIM
A%
indicates that A is an integer variable.!
in DIM
B!
indicates that B is a single precision float variable.#
in DIM
C#
indicates that C is a double precision float variable.$
in DIM
D$
indicates that D is a string variable.
A data type-declaration suffix also can be expressed using the AS
keyword in combination with the DIM
statement, for example,
DIM
VarSCAS SCHAR
would dimension VarSC as a short char.
Here is a fundamental list showing how some data types can be declared using the
AS
keyword.
Dimensioning Integers
Syntax: DIM a AS SCHAR Purpose: Allocates space for signed char variable named a Remarks: Signed char can hold Minimum -128 Maximum 127 DO NOT USE Syntax: DIM a AS SIGNED CHAR 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%
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
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:
|
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:
|
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:
|
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
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:
|
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
zDIM
VOLATILE
aAS
INTEGER
DIM
c[
20
]
AS
CHAR
END
TYPE
DIM
VOLATILE
dddGLOBAL
VOLATILE
aaaAS
INTEGER
SHARED
VOLATILE
BBBAS
zEXTERN
VOLATILE
cccAS
INTEGER
SUB
v(
)
STATICVOLATILE
zzAS
zEND
SUB
Variable Scope
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 dont(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 DIM 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!
"The Value Of A! = "
, A!"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
'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.
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 a SUB or FUNCTION (Console mode only)
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 will automatically free the memory
allocated.
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 FREE
d,
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 DIM RAW or AUTO LOCAL statement
Purpose: DIM RAW
or its alias AUTO LOCAL
can be used
to create uninitialized variables. DIM RAW
does not clear the memory block of the created variable by filling it with zeros.
A DIM RAW
variable created in a subroutine
or function is not DIM STATIC
.
Syntax 1:
DIM RAW VariableName
Parameters:
|
Remarks:
DIM RAW does not initialize variables inside SUB or FUNCTION procedures, for example,
SUB RawSub1() DIM RAW str1$ END SUB
translates to C source code
void RawSub1(void) { char str1[2048]; }
while
SUB RawSub1() DIM str1$ END SUB
translates to C source code
void RawSub1(void) { static char str1[2048]; memset(&str1,0,sizeof(str1)); }
DIM RAW
used outside of a SUB or FUNCTION
procedure is the same as STATIC
, for example,
DIM RAW a$
translates to C source code
static char a [2048];
LOCAL
variables slow things down a bit because they need
to be zero'd out each time. DIM 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
array2%[
3
]
=
{2
,4
,8
}[
0
]
[
1
]
[
2
]
The value of array2%[0] is 2, array2%[1] is 4 and array2%[2] is 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
array2%[
3
]
=
{2
,4
}[
0
]
[
1
]
[
2
]
The value of array2%[0] is 2, array2%[1] is 4 and array2%[2] is 0.
Example 3: shows how to specify which elements of an array are to be initialized.
DIM
array2%[
3
]
=
{[
0
]
=
2
,[
2
]
=
8
}[
0
]
[
1
]
[
2
]
The value of array2%[0] is 2, array2%[1] is 0 and array2%[2] is 8.
Example 4: shows how to initialize elements of an array in which the index size is not specified.
DIM
array2%[
]
=
{2
,4
,8
}[
0
]
[
1
]
[
2
]
Because no index size was specified for array2%, three initialized elements are defined by the compiler. As in Example 1, the value of array2%[0] is 2, array2%[1] is 4 and array2%[2] is 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
Ind0DIM
Ind1DIM
Ind2DIM
RAW
Array[
3
]
=
{&
Ind0,&
Ind1,&
Ind2}AS
LPVOID Ind0=
1
Ind1=
2
Ind2=
3
CALL
Ordinate(
)
SUB
Ordinate(
)
DIM
iAS
INTEGER
STORE(
i,Array[
0
]
)
:(
i,Array[
1
]
)
:(
i,Array[
2
]
)
:END
SUB
CONST
STORE(
src,des)
memmove(
&
src,des,SIZEOF
(
src)
)
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:
|
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.
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 are automatically
freed.
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)
[
I]
NEXT
FREE
A$' Release memory back to the operating system
FREE
A$' An intentional error -- BCX handles it automatically
CALL
FOO_TESTSUB
FOO_TEST(
)
DIM
RAW
E=
100
DIM
DYNAMIC
A$[
E]
DIM
DYNAMIC
B$[
E]
DIM
DYNAMIC
C$[
E]
DIM
DYNAMIC
D$[
E]
"Storing Items In A$[]"
FOR
INTEGER
I=
1
TO
E A$[
I]
=
"A$[] ... THIS IS LINE "
&
STR$
(
I)
NEXT
"Storing Items In B$[]"
FOR
INTEGER
I=
1
TO
E B$[
I]
=
"B$[] ... THIS IS LINE "
&
STR$
(
I)
NEXT
"Storing Items In C$[]"
FOR
INTEGER
I=
1
TO
E C$[
I]
=
"C$[] ... THIS IS LINE "
&
STR$
(
I)
NEXT
"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[
I]
[
I]
[
I]
[
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
rctAS
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(
rctAS
RECTPTR
)
? rct-
>left ? rct-
>top ? rct-
>right ? rct-
>bottomEND
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
pstr1AS
CHAR
PTR
pstr1=
str1$ Increment(
&
pstr1)
SUB
Increment(
ppstr1AS
CHAR
PTR
PTR
)
+
+
*
ppstr1END
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:
|
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:
|
Syntax 2: REDIM PRESERVE ArrayX[Index] Parameters:
|
Syntax 3: REDIM PRESERVE Array[Index1, Index2, Index3, Index4] AS data type Parameters:
|
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."
[
2
]
.A$[
2
]
.B$[
8
]
.A$[
8
]
.B$
Example 2: This example redimensions a dynamic string.
DIM b$ * 10 b$ = "1234567890" ? b$ REDIM b$ * 14 b$ = b$ & "ABCD" ? b$ REDIM PRESERVE b$ * 20 b$ = b$ & "EFGHIJKLMNOPQRST" ? b$
Example 3: This example redimensions a single dimension array.
DIM DYNAMIC a$[20] REDIM a$[10] a$[0] = "this" a$[1] = "is" a$[2] = "a" a$[3] = "test" REDIM PRESERVE a$[20] ? a$[0] ? a$[1] ? a$[2] ? a$[3]
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] DIM DYNAMIC cc$[100] CALL x CALL FREEGLOBALS SUB x GLOBAL DYNAMIC bb$[100] GLOBAL d$ * 100 GLOBAL e$ * 100 GLOBAL f$ * 100 GLOBAL g$ * 100 DIM h$ * 100 END SUB
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% FillAS
BOOL
END
TYPE
DIM
abcAS
MYBOX abc.Top%=
100
abc.Left%=
300
abc.Width%=
100
abc.Height%=
100
abc.Fill=
TRUE
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 idAS
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_TYPDYNAMIC
prev_node[
]
AS
NODE_TYPEND
TYPE
DIM
FAS
NODE_TYPDIM
XAS
NODE_TYPPTR
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
FirstEND
TYPE
DIM
DYNAMIC
Third[
6
]
AS
SecondDIM
i, iiFOR
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]
.SomethingNEXT
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 aAS
INTEGER
b$ cAS
SINGLE
END
UNION
DIM
BloofAS
Foo Bloof.a=
1
=
"Hello, World!"
=
3.14159
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 dwConfigAS
DWORD
UNION
formatTYPE
mp3 dwSampleRateAS
DWORD
byModeAS
BYTE
wBitrateAS
WORD
bPrivateAS
BOOL
bCRCAS
BOOL
bCopyrightAS
BOOL
bOriginalAS
BOOL
END
TYPE
TYPE
aac dwSampleRateAS
DWORD
byModeAS
BYTE
wBitrateAS
WORD
byEncodingMethodAS
BYTE
END
TYPE
END
UNION
END
TYPE
DIM
TAS
BE_CONFIG T.format.mp3.byMode=
1
T.format.mp3.bPrivate=
2
T.format.aac.byEncodingMethod=
3
T.format.aac.dwSampleRate=
44100