[comp.sys.atari.st] Stack and other C variable-storage spaces

braner@batcomputer.tn.cornell.edu (braner) (12/18/86)

[For C programmers only.  Somewhat long.  Boring for experts.]

Now that I have Megamax version 1.1, I looked again at some programs which
seemed to suffer from obscure bugs under version 1.0.  I found out that
some of the problems were MY fault.  The following might be old hat to you
C experts, but I have the impression that many ST users are new to C, so
here goes...

When you declare a variable inside a C function, it exists only until you
return from the function.  This is called an "automatic" variable.  In
most implementations of C automatic variables are stored on the "stack",
together with the return addresses for functions, and arguments passed to
functions.  The stack "height" fluctuates as functions are called in a
(hopefully not endless) chain.  Variables declared in main() are also
"automatic".

The amount of "stack" memory space allotted to a program is finite.  If
your automatic variables, at some point during the execution of your
program, exceed the allotted space, ANYTHING might happen.  Printf()
statements might print garbage, the system may crash, etc.  The compiler
has no way of predicting whether your program will pass that red line,
since items are stacked dynamically while the program runs.  For example,
a recursive algorithm will build up a large stack, with a height dependent
on the data worked on.

A buggy program:

	main()
	{
		double	a[30][30];
		...
	}

The Megamax C compiler allots 8K of RAM for the stack.  If your program
needs more, that is usually because of large automatic arrays.  For
example, the 30-by-30 array of doubles is 7200 bytes, and the other uses
of the stack may very well take it beyond the 8K limit!  There are a
number of solutions:

You can recompile the "init.c" module, allotting more stack space.
Megamax will let you allot up to 32K (perhaps 64K?) of stack space.
(You'll have to replace the init.o module in syslib with your modified
one.) This lets you leave the program as-is, and is sometimes necessary,
but usually not.

Alternatively, you can declare your arrays outside of any function:

	double	a[30][30];
	main()
	{
		...
	}

- but this way the variable is "visible" to all functions.  Alternatively,
you can declare the array inside a function but as "static":

	main()
	{
		static double	a[30][30];
		...
	}

These non-automatic variables are put into what's called the "bss segment",
and in Megamax this segment is limited to 32K.  Not very much, but more
than 8K...

A third way to allocate large arrays is via the malloc() function:

	main()
	{
		double **a;
		extern char *malloc();
		a = (double **) malloc(30*30*sizeof(double));
		if (a == 0)		/* not enough memory available	*/
			exit (0);	/* do something about it	*/
		...
		x = a[i][j];		/* use like any array...	*/
	}

The malloc() function (not to be confused with the OS Malloc() call, which
is to be avoided like the plague...) returns a pointer to an area allotted
during run-time.  The amount of RAM needed is passed to malloc() as an
argument of type unsigned int.

If you wonder how can you allot more than 64K when the argument to malloc()
is a 16-bit integer, the answer is: you cannot.  If you really need such
a large consecutive space, you'll have to tinker with the dreaded Malloc(),
which uses a long (32-bit) argument.  But you can do a lot with malloc().
Here is a way to set up an array of 3000-by-3000 doubles (you'll need a
133120ST to do it, though):

	main()
	{
		int i;
		double **a;
		extern char *malloc();
	/* set up an array of 3000 pointers:	*/
		a = (double **) malloc(3000*sizeof(double *));
		if (a == 0)  exit (1);
	/* allocate memory for 3000 arrays, each of 3000 doubles:	*/
		for (i=0; i<3000; i++) {
			a[i] = (double *) malloc(3000*sizeof(double));
			if (a[i] == 0)  exit (2);
		}
	/* use like any array!	*/
		x = a[i][j];
	}

Not only did we allocate a 72 Megabyte pseudo-array with malloc(), this
set-up actually works faster than a real 2-dimensional array because the
addresses of each row are preset in the array-of-pointers.

I hope this helps some of you who are wrestling with C on the ST.

- Moshe Braner

braner@batcomputer.tn.cornell.edu (braner) (12/18/86)

[]

Oops... I goofed.

The declaration of the variable 'a' in one of the examples in a previous
posting of mine about C variable storage was wrong!  It said:

A third way to allocate large arrays is via the malloc() function:

	main()
	{
		double **a;
		extern char *malloc();
		a = (double **) malloc(30*30*sizeof(double));
		...
		x = a[i][j];		/* use like any array...	*/
	}

It should have said:

	main()
	{
		double **a;
		extern char *malloc();
		a = (double **) malloc(30*30*sizeof(double));
		...
		x = a[30*i+j];		/*** instead of a[i][j]	***/
	}

Alternatively, and probably better style:

	main()
	{
		double (*a)[30];      /*** a pointer to a 30-long array ***/
			/* (same as "typedef double D30[30]; D30 *a;")	*/
		extern char *malloc();
		a = (double **) malloc(30*30*sizeof(double));
		...
		x = a[i][j];		/* now use like any 2-d array... */
	}

Note that the declaration "double **a;" in the LAST example in the previous
posting IS correct: I actually set up an array of pointers there!  That last
method, as mentioned, is faster, and could be used in the 30-by-30 case too!
It is also helpful in writing library functions which act on 2-d arrays of
a size unknown at compile time:

func(n, a)
	int	n;		/* dimension, determined at run time	*/
	double	**a;		/* a pointer to an array of pointers	*/
{
	for (i=0; i<n; i++)	/* loop the right number of times	*/
		x = a[i][j];	/* use a[][] as if it was a 2-d array	*/
	...
}

Later, in a different file:

#include "func.c"
...
main()
{
	double *a[7];		/* array of pointers			*/
	double aa[7][7];	/* the physical array of doubles	*/
	for (i=0; i<7; i++)
		a[i] = &aa[i][0];	/* have to initialize a[]!	*/
	double *b[5];		/* another one, of different size	*/
	double bb[5][5];
	for (i=0; i<5; i++)
		b[i] = &bb[i][0];
	...
	x = func(7,a);
	y = func(5,b);
}

The ability to do this (along with the availability of pointers-to-functions)
is a major advantage of C over, say, FORTRAN.

- Moshe Braner