This document contains some notes about developing C programmes which use
large ammounts of memory. It was inspired by my efforts to develop code for
a finite element analysis programe and the dificulties I had using the IDE,
Debugger and Profiler. I only discuss the Large memory model and __far
pointers. All code was developed using the ANSI C setting, under the Borland
C++ 3.1 integrated development environment.

/* ANSI C, large model, stack checking, std frame */
/* This sample compiles and links with no error, but the counter
	goes to 0 after 75 on my machine. Execution never terminates. */
/* Tab size=2 */

#include <stdio.h>

typedef struct	/* a typical structure */
{
	char	*name;	/* a location to put a name string if required */
	float	x,y,z;	/* coordinates of a point in space */
} Node;
/* The Node structure in the medium and up models has a size of 16bytes */
/* The data segment is 64k bytes so there is supposed to be room for 4096 */
/* assuming nothing else uses the data segement */

extern unsigned _stklen = 0xff00;
/* without the _stklen command (which may cause a duplicate symbol warning) */
/* the programme fails with a Stack Overflow! warning if stack overflow is */
/* being checked for. See the Options|Compiler|Entry/Exit Code option in the */
/* IDE. */

int	main(int argc,char *argv[])
{
	int t,i;

	Node	a[1000];	/* These arrays need 16*1,000=16,000 bytes each */
	Node	b[1000];	/* The total requirement is 96,000 bytes */
	Node	c[1000];	/* This will cause the programme as given to fail. */
	Node	d[1000];	/* The compiler will NOT detect the problem. */
	Node	e[1000];
	Node	f[1000];

	for(i=0;i<1000;i++)	/* do something with all the allocated elements */
	{
		a[i].x=a[i].y=a[i].z=0.0; a[i].name=NULL;
		b[i].x=b[i].y=b[i].z=0.0; b[i].name=NULL;
		c[i].x=c[i].y=c[i].z=0.0; c[i].name=NULL;
		d[i].x=d[i].y=d[i].z=0.0; d[i].name=NULL;
		e[i].x=e[i].y=e[i].z=0.0; e[i].name=NULL;
		f[i].x=f[i].y=f[i].z=0.0; f[i].name=NULL;
	}
	i=sizeof(Node);	/* You can inspect i just to check it is 16. */
	for (t=0; t<argc; ++t)
	{
		i=0;
		while(argv[t][i])
		{
			printf("%c",argv[t][i]);
			++i;
		}
	}
	return 0;
}

I had no trouble stepping through this programme in the IDE. It never
completed the loop initialising the arrays.

The next step is to move some of the data out of the programme near heap, if
I understand things correctly. This is done by using the static keyword.

/* ANSI C, large model, stack checking, std frame */
/* This sample compiles and links with no error, but the counter
	goes to 0 after 75 */
/* Tab size=2 */

#include <stdio.h>

typedef struct
{
	char __far *name;	/* note the addition of __far */
	float	x,y,z;
} Node;

/*extern unsigned _stklen = 0xff00; */
/* The need for the stklen modifier is removed and the arrays are all on */
/* the far heap. */

int	main(int argc,char *argv[])
{
	int t,i;
	static __far Node	a[3000];	/* Total required space is now 160,000 bytes */
	static __far Node	b[3000];
	static __far Node	c[1000];
	static __far Node	d[1000];
	static __far Node	e[1000];
	static __far Node	f[1000];

	for(i=0;i<3000;i++)
	{
		a[i].x=a[i].y=a[i].z=0.0; a[i].name=NULL;
		b[i].x=b[i].y=b[i].z=0.0; b[i].name=NULL;
	}
	for(i=0;i<1000;i++)
	{
		c[i].x=c[i].y=c[i].z=0.0; c[i].name=NULL;
		d[i].x=d[i].y=d[i].z=0.0; d[i].name=NULL;
		e[i].x=e[i].y=e[i].z=0.0; e[i].name=NULL;
		f[i].x=f[i].y=f[i].z=0.0; f[i].name=NULL;
	}
	i=sizeof(Node);
	for (t=0; t<argc; ++t)
	{
		i=0;
		while(argv[t][i])
		{
			printf("%c",argv[t][i]);
			++i;
		}
	}
	return 0;
}

This programme compiles fine, and runs under the IDE or direct from DOS. The
next stage is to use malloc() to allocate space at run time.


#include	<stdio.h>
#include	<stdlib.h>
/* #include	<alloc.h> */
/* alloc.h could be used, but exit() is in stdlib.h, and apparently so are
	 the malloc() and free() header definitions */

typedef struct
{
	char __far *name;
	float	x,y,z;
} Node;

int	main(int argc,char *argv[])
{
	int t,i;
	Node	__far *a;	/* use pointers for a and b, and malloc() to allocate */
	Node	__far *b;	/* space for them */
	static __far Node	c[1000];	/* dont have to make these all static */
	static __far Node	d[1000];	/* experiment in your aplication */
	static __far Node	e[1000];
	static __far Node	f[1000];

	if(!(a=(Node __far *)malloc(sizeof(Node)*3000)))
	{
		printf("Exit1\n");	/* let the user know where it failed */
		exit(0);
	}
	if(!(b=(Node __far *)malloc(sizeof(Node)*3000)))
	{
		printf("Exit2\n");
		free(a);	/* dont forget to release memory already allocated */
		exit(0);
	}
	for(i=0;i<3000;i++)
	{
		a[i].x=a[i].y=a[i].z=0.0; a[i].name=NULL;
		b[i].x=b[i].y=b[i].z=0.0; b[i].name=NULL;
	}
	for(i=0;i<1000;i++)
	{
		c[i].x=c[i].y=c[i].z=0.0; c[i].name=NULL;
		d[i].x=d[i].y=d[i].z=0.0; d[i].name=NULL;
		e[i].x=e[i].y=e[i].z=0.0; e[i].name=NULL;
		f[i].x=f[i].y=f[i].z=0.0; f[i].name=NULL;
	}
	i=sizeof(Node);
	for (t=0; t<argc; ++t)
	{
		i=0;
		while(argv[t][i])
		{
			printf("%c",argv[t][i]);
			++i;
		}
	}
	free(a);	/* release memory */
	free(b);
	return 0;
}

Stepping through this version in the IDE, it will probably fail on one or
other of the allocations. Select Options|Debugger and set the programme heap
size to something greater than 64kb. I put it at 200kb to get a successful
step through, but that value may be larger than required.

Some Suggestions for improving consistancy etc.

#define a pointer to the data structure, like this;

#define	NODEPTR	Node __far *

Then use the following form for the malloc()

	if( !( a=(NODEPTR)malloc(sizeof(Node)*amount) ) )
	{
		/* failure handling */
	}

Also use the parameter form;

_type	foo(NODEPTR param);

This technique allows you to use a consistent pointer to the Node type,
without the danger of forgetting the __far.

I also tend to use a define for __far so that it can be set to whatever I
want in the entire programme without finding all the instances individualy.

e.g.

#ifdef	_STDC_
#define	FAR	__far
#else
#define	FAR	far
#endif

Some comments about huge.

In the examples above, memory allocation must be within segment boundarys.
The address form aaaa:bbbb imposes a limit of 64kb on incrementing a pointer
into the array. For a full explanation see TI738, Memory Corruption. In
outline, the bbbb part is incremented or decremented when a far pointer is
used, but when it is incremented past FFFF or decremented past 0000, the aaaa:
part is not changed. Huge pointers on the other hand are maintained in a
normalised form so they have the form aaaa:000b where b can have values from
0 to F. Incrementing past F adds one to the aaaa: part.
If you didn't follow that, read a good book on assembler or the TI738
bulletin from Borland.

Some other bulletins you might find useful are:

TI1007, dealing with large amounts of string data
TI1008, allocmem v malloc
TI1151, source for new()
TI1711, functions to find size of block allocated
TI1723, Function that returns the total heap memory
TI726, Null Pointer Assignment Errors Explained
TI813, Dealing with Stack Overflow Problems.

Note that using far pointers imposes extra overhead, and using huge pointers
imposes further overhead, in handling the actual pointers over the smaller
memory models. This increases execution time and should be bourne in mind
when designing your application.

Using the profiler.

The profiler may not be able to load a programme which uses a large amount of
static or local data. Version 2.1 gave me an error message 'Error loading
programme' when I tried to profile programme version two above by transfering
from the IDE. When I re-tried after EXITing to DOS, it loaded the programme,
but wouldnt run it. When I unloaded a driver from memory, the programme
loaded and ran without problems. Version three wouldnt run past the first
malloc() under the profiler. If you can get the programme loaded, but not
run, look under File|Get Info, to see what space is available. There are
command line options to increase available memory. Refer to the Help or your
manual.

Some Other comments.

Having got your programme to run, there are some other problems to be dealt
with. In particular, I have found that passing an array to a function and
then using pointer arithmetic to access elements can be hazardous. The
function may have no way of knowing that the array size is 100, and that it
has incremented past that value. This effect was crashing a programme I
worked on recently. In main, a function was called with a pointer like this
in the parameter list.

	i+=foo( (arry+i), ....... );

In the function, the array was being accessed from the point passed, using
another index. The return value was the final value of this index. There was
no way of the function knowing the upper limit of the array, and no way of
knowing ahead of time that the index would exceed that value since it might
only increase by two or three within the function.

	i+=foo( (arry+i),i,maxi, ....... );

became the parameter list. Within the function:

int	foo(ARRYPTR arry,int i,int maxi, ........... )
{
	int j=0;
	..
	do
	{
		..
		(arry+j)=f1();
		j++;
		assert(j+i<maxi);
		..
	}while(!Finished);
	..
	return j;
}

This is just one suggestion. It works well, but is not perfect. I leave you
to find the perfect solution, but it will be different in each programme.

This is not a complete discussion. It is not intended to be complete. It is
intended to give some hints and tips. I hope it is useful. If so, great!

Dave Bolt [100112,522]
