Sunday, December 6, 2009

Dynamic Memory Allocation

If a program needs a table of data, but the size of the table is variable (perhaps for a list of all filenames in the current directory), it is inefficient to waste memory by declaring a data table of the maximum possible size. It is better to dynamically allocate the table as required.



Turbo C allocates RAM as being available for dynamic allocation into an area called the heap. The size of the heap varies with memory model. The tiny memory model defaults to occupy 64 K of RAM. The small memory model allocates up to 64 K for the program/code and heap with a far heap, being available within the remainder of conventional memory. The other memory models make all conventional memory available to the heap. This is significant when programming in the tiny memory model, when you want to reduce the memory overhead of your program. The way to do this is to reduce the heap to a minimum size (the smallest is 1 byte).



C provides the function malloc( ) to allocate a block of free memory of a specified size and to return a pointer to the start of the block; it also provides free( ), which deallocates a block of memory previously allocated by malloc( ). Notice, however, that the PC does not properly free blocks of memory, therefore continuous use of malloc( ) and free( ) will fragmentize memory, eventually causing memory outage until the program terminates.



This sample program searches a specified file for a specified string, with case-sensitivity. It uses malloc( ) to allocate just enough memory for the file to be read into memory:



#include #include



char *buffer;



void main(int argc, char *argv[])

{

FILE *fp; long flen;



/* Check number of parameters */

if (argc != 3)

{

fputs("Usage is sgrep ",stderr);

exit(0);

}



/* Open stream fp to file */ fp = fopen(argv[2],"r");

if (!fp)

{

perror("Unable to open source file");

exit(0);

}



/* Locate file end */

if(fseek(fp,0L,SEEK_END))

{ fputs("Unable to determine file length",stderr); fclose(fp);

exit(0);





/* Determine file length */

flen = ftell(fp);



/* Check for error */

if (flen == -1L)

{

fputs("Unable to determine file length",stderr); fclose(fp);

exit(0);

}



/* Set file pointer to start of file */ rewind(fp);



/* Allocate memory buffer */ buffer = malloc(flen);



if (!buffer)

{

fputs("Unable to allocate memory",stderr); fclose(fp);

exit(0);

}



/* Read file into buffer */ fread(buffer,flen,1,fp);



/* Check for read error */

if(ferror(fp))

{

fputs("Unable to read file",stderr);



/* Deallocate memory block */ free(buffer);



fclose(fp);

exit(0);

}



printf("%s %s in %s",argv[1],(strstr(buffer,argv[1])) ? "was foun d" : "was not found",argv[2]);



/* Deallocate memory block before exiting */ free(buffer); fclose(fp);

}



Atexit

Whenever a program terminates, it should close any open files (this is done for you by the C compiler's startup/termination code with which it surrounds your program) and restore the host computer to some semblance of order. Within a large program, where exit may occur from a number of locations, it is tiresome to have to continually write calls to the cleanup routine. Fortunately, we don't have to.



The ANSI standard on C describes a function called atexit( ) that registers the specified function, supplied as a parameter to atexit( ), as a function that is called immediately before terminating the program. This function is called automatically, so the following program calls leave( ), whether an error occurs or not:



#include



void leave()

{

puts("\nBye Bye!");

}



void main()

{

FILE *fp; int a; int b; int c; int d; int e;

char text[100];

atexit(leave);

fp = fopen("data.txt","w");

if(!fp)

{

perror("Unable to create file");

exit(0);

}

fprintf(fp,"1 2 3 4 5 \"A line of numbers\""); fflush(fp);

if (ferror(fp)) {

fputs("Error flushing stream",stderr); exit(1);

}



rewind(fp);

if (ferror(fp))

{

fputs("Error rewind stream",stderr); exit(1);

fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text);

if (ferror(fp)) {

/* Unless you noticed the deliberate bug earlier */ /* The program terminates here */ fputs("Error reading from stream",stderr); exit(1);

}



printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text);

}



Increasing Speed



In order to reduce the time your program spends executing, it is essential to know your host computer. Most computers are very slow at displaying information on the screen. C offers various functions for displaying data, printf ( ) being one of the most commonly used and also the slowest. Whenever possible, try to use puts(varname) in place of printf(''%s\ n",varname), remembering that puts( ) appends a newline to the string sent to the screen.



When multiplying a variable by a constant, which is a factor of 2, many C compilers will recognize that a left shift is all that is required in the assembler code to carry out the multiplication rapidly. When multiplying by other values, it is often faster to do a multiple addition instead, where:



• x * 3' becomes 'x + x + x'



Don't try this with variable multipliers in a loop because it will drag on slowly. Fortunately, when the multiplier is a constant it can be faster.



Another way to speed up multiplication and division is with the shift commands, << and >>. The instruction x /= 2 can be written as x >>= 1 (shift the bits of x right one place). Many compilers actually convert integer divisions by 2 into a shift-right instruction. You can use the shifts for multiplying and dividing by 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, and so on. If you have difficulty understanding the shift commands, consider the binary form of a number:

01001101 is equal to 77

The preceding example shifted right one place becomes:

00100110 is equal to 38



Try to use integers rather than floating-point numbers wherever possible. Sometimes you can use integers where you didn't think you could. For example, to convert a fraction to a decimal you would normally use:

percentage = x / y * 100

This requires floating-point variables. However, it can also be written as: z = x * 100;

percentage = z / y Directory Searching



The functions "find first" and "find next" are used to search a DOS directory for a specified file name or names. The first function, "find first," is accessed via DOS interrupt 21, function 4E. It takes an ASCII string file specification, which can include wildcards, and the required attribute for files to match. Upon return, the function fills the disk transfer area (DTA) with details of the located file, and returns with the carry flag clear. If an error occurs, such as "no matching files have been located," the function returns with the carry flag set.



Following a successful call to ''find first," a program can call "find next," DOS interrupt 21, function 4F, to locate the next file matching the specifications provided by the initial call to "find first." If this function succeeds, then the DTA is filled in with details of the next matching file, and the function returns with the carry flag clear. Otherwise, a return is made with the carry flag set.



Most C compilers for the PC provide nonstandard library functions for accessing these two functions. Turbo C provides findfirst( ) and findnext( ). (Making use of the supplied library functions shields the programmer from the messy task of worrying about the DTA.) Microsoft C programmers should substitute findfirst( ) with _dos_findfirst( ), and findnext( ) with _dos_findnext( ).



The following Turbo C example imitates the DOS directory command, in basic form:



#include #include #include



void main(void)

{

/* Display directory listing of current directory */







int done;

int day;

int month;

int year;

int hour;

int min;

char amflag;

struct ffblk ffblk;

struct fcb fcb;



/* First display sub directory entries */ done = findfirst("*.",&ffblk,16);



while (!done)

{

year = (ffblk.ff_fdate >> 9) + 80; month = (ffblk.ff_fdate >> 5) & 0x0f; day = ffblk.ff_fdate & 0x1f; hour = (ffblk.ff_ftime >> 11); min = (ffblk.ff ftime >> 5) & 63;

if (hour > 12) {

hour -= 12; amflag = 'p'

}



printf("%-11.11s %02d-%02d-%02d %2d:%02d%c\n", ffblk.ff_name,day,month,year,hour,min,amflag); done = findnext(&ffblk);

}

/* Now all files except directories */ done = findfirst("*.*",&ffblk,231);



while (!done)

{

year = (ffblk.ff_fdate >> 9) + 80; month = (ffblk.ff_fdate >> 5) & 0x0f;

day = ffblk.ff_fdate & 0x1f; hour = (ffblk.ff_ftime >> 11);

min = (ffblk.ff_ftime >> 5) & 63;



amflag = 'a';



if (hour > 12)

{

hour -= 12;







amflag = 'p';

}

parsfnm(ffblk.ff_name,&fcb,1);



printf("%-8.8s %-3.3s %8ld %02d-%02d-%02d %2d:%02d%c\n",

fcb.fcb_name,fcb.fcb_ext,ffblk.ff_f size, day,month,year,hour,min,amflag); done = findnext(&ffblk);

}

}



The function parsfnm( ) is a Turbo C library command, which makes use of the DOS function for parsing an ASCII string containing a filename into its component parts. These component parts are then put into a DOS file, control block (fcb), from where they may be easily retrieved for display by printf(). The DOS DTA is composed as follows:

OFFSET LENGTH

00 15 15 Byte

CONTENTS

Reserved

Attribute of matched file

16 Word File time

18 Word File date

1A 04 File size

1E 0D File name and extension as ASCII string



The file time word contains the time at which the file was last written to disk and is composed as follows:

BITS CONTENTS

0 - 4 Seconds divided by 2

5 - 10 Minutes

11 - 15 Hours



The file date word holds the date on which the file was last written to disk and is composed of:



BITS CONTENTS

0 - 4 Day

5 - 8 Month

9 - 15 Years since 1980







To extract these details from the DTA requires a little manipulation, as illustrated in the previous example. The DTA attribute flag is composed of the following bits being set or not:



BIT


ATTRIBUTE

0


Read only

1


Hidden

2


System

3


Volume label

4


Directory

5


Archive



Accessing Expanded Memory



Memory (RAM) in a PC comes in three flavors, conventional, expanded, and extended. Conventional memory is the 640K of RAM, which the operating system DOS can access. This memory is normally used; however, it is often insufficient for current RAM-hungry systems. Expanded memory is RAM that is addressed outside of the area of conventional RAM, not by DOS but by a second program called a LIM EMS driver. Access to this device driver is made through interrupt 67h.



The main problem with accessing expanded memory is that no matter how much expanded memory is added to the computer, it can be accessed only through 16K blocks referred to as pages. So if you have 2 MB of expanded RAM allocated for a program, then that is composed of 128 pages (128 * 16K = 2MB). A program can determine whether a LIM EMS driver is installed by attempting to open the file EMMXXXX0, which is guaranteed by the LIM standard to be present as an IOCTL device when the device driver is active.



The following source code illustrates some basic functions for testing for and accessing expanded memory:



#include #define EMM 0x67



char far *emmbase; emmtest()

{

/*

Tests for the presence of expnaded memory by attempting to

open the file EMMXXXX0.

*/

union REGS regs;



struct SREGS sregs; int error; long handle;

/* Attempt to open the file device EMMXXXX0 */ regs.x.ax = 0x3d00; regs.x.dx = (int)"EMMXXXX0"; sregs.ds = _DS; intdosx(®s,®s,&sregs); handle = regs.x.ax; error = regs.x.cflag;

if (!error) {

regs.h.ah = 0x3e; regs.x.bx = handle; intdos(®s,®s);

}

return error;

}



emmok()

{

/*

Checks whether the expanded memory manager responds correctly

*/

union REGS regs;

regs.h.ah = 0x40; int86(EMM,®s,®s);



if (regs.h.ah) return 0;

if (regs.h.ah) return 0;



emmbase = MK_FP(regs.x.bx,0); return 1;

}

long emmavail() {

/*

Returns the number of available (free) 16K pages of expanded memo

ry

or -1 if an error occurs.







*/



union REGS regs;




regs.h.ah = 0x42; int86(EMM,®s,®s); if (Iregs.h.ah)

return regs.x.bx; return -1;

}



long emmalloc(int n)

{

/*

Requests 'n' pages of expanded memory and returns the file handle

assigned to the pages or -1 if there is an error

*/



union REGS regs;



regs.h.ah = 0x43; regs.x.bx = n; int86(EMM,®s,®s); if (regs.h.ah)

return -1; return regs.x.dx;

}



emmmap(long handle, int phys, int page)

{

/*

Maps a physical page from expanded memory into the page frame in

the

conventional memory 16K window so that data can be transferred be tween the expanded memory and conventional memory.





union REGS regs;

regs.h.ah = 0x44; regs.h.al = page; regs.x.bx = phys; regs.x.dx = handle; int86(EMM,®s,®s); return (regs.h.ah == 0);

}

void emmmove(int page, char *str, int n)

{

/*

Move 'n' bytes from conventional memory to the specified expanded



memory page

*/



char far *ptr;



ptr = emmbase + page * 16384; while(n-- > 0) *ptr++ = *str++;

}

void emmget(int page, char *str, int n)

{

/*

Move 'n' bytes from the specified expanded memory page into conve ntional memory

*/



char far *ptr;



ptr = emmbase + page * 16384; while(n-- > 0) *str++ = *ptr++;

}



emmclose(long handle)

{

/*

Release control of the expanded memory pages allocated to 'handle

'

*/



union REGS regs;

regs.h.ah = 0x45;

regs.x.dx = handle; int86(EMM,®s,®s); return (regs.h.ah == 0);





/*

Test function for the EMM routines */



void main()

{

long emmhandle; long avail; char teststr[80]; int i;







if(!emmtest())

{

printf("Expanded memory is not present\n");

exit(0);

}



if(!emmok())

{

printf("Expanded memory manager is not present\n");

exit(0);

}

avail = emmavail(); if (avail == -1)

{

printf("Expanded memory manager error\n");

exit(0);

}

printf("There are %ld pages available\n",avail);



/* Request 10 pages of expanded memory */ if((emmhandle = emmalloc(10)) < 0)

{

printf("Insufficient pages available\n");

exit(0);

}



for (i = 0; i < 10;

{

sprintf(teststr,"%02d This is a test string\n",i); emmmap(emmhandle,i,0);

emmmove(0,teststr,strlen(teststr) + 1);

}



for (i = 0; i < 10; i++)

{

emmmap(emmhandle,i,0);

emmget(0,teststr,strlen(teststr) + 1); printf("READING BLOCK %d: %s\n",i,teststr);





emmclose(emmhandle);

}



Accessing Extended Memory



Extended memory has all but taken over from expanded memory, as it is faster and more useable than expanded memory. As with expanded memory, however, extended memory cannot be directly accessed through the standard DOS mode; therefore, a transfer buffer in conventional or "real mode" memory must be used. The process to write data to extended memory involves copying the data to the transfer buffer in conventional memory, and from there, copying it to extended memory.



Before any use may be made of extended memory, a program should test to see if it is available. The following function, XMS_init( ), tests for the presence of extended memory; if it is available XMS_init( ) calls another function, GetXMSEntry( ), to initialize the program for using extended memory. The function also allocates a conventional memory transfer buffer:



/*

BLOCKSIZE will be the size of our real-memory buffer that we'll swap XMS through (must be a multiple of 1024, since XMS is allocated in 1K chunks.)

*/



#ifdef __SMALL__

#define BLOCKSIZE (16L * 1024L)

#endif



#ifdef __MEDIUM__ #define BLOCKSIZE (16L * 1024L)

#endif



#ifdef __COMPACT__ #define BLOCKSIZE (64L * 1024L)

#endif



#ifdef __LARGE__

#define BLOCKSIZE (64L * 1024L)

#endif



char XMS_init()

{

/*

returns 0 if XMS present,

if XMS absent

if unable to allocate conventional memory transfer buffer

*/

unsigned char status;

_AX=0x4300;

geninterrupt(0x2F); status = _AL;

if(status==0x80)

{

GetXMSEntry();



XMSBuf = (char far *) farmalloc(BLOCKSIZE); if (XMSBuf == NULL)

return 2; return 0;

}

return 1;

}



void GetXMSEntry(void)

{

/*

GetXMSEntry sets XMSFunc to the XMS Manager entry point so we can call it later

*/



_AX=0x4310; geninterrupt(0x2F);

XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);

}



Once the presence of extended memory has been confirmed, the following program can find out how much of it is available:



void XMSSize(int *kbAvail, int *largestAvail)

{

/*

XMSSize returns the total kilobytes available, and the size in kilobytes of the largest available block

*/



_AH=8;

(*XMSFunc)();

*largestAvail=_DX;

*kbAvail=_AX;

}



The next function may be called to allocate a block of extended memory, as you would allocate a block of conventional memory:



char AllocXMS(unsigned long numberBytes)

{

/*

Allocate a block of XMS memory numberBytes long Returns 1 on success 0 on failure

*/



_DX = (int)(numberBytes / 1024);

_AH = 9; (*XMSFunc)();

if (_AX==0) {

return 0;

}

XMSHandle=_DX;

return 1;

}



DOS does not automatically free allocated extended memory. A program using extended memory must release it before terminating. This function frees a block of extended memory previously

allocated by AllocXMS:



void XMS_free(void)

{

/*

Free used XMS

*/

_DX=XMSHandle;

_AH=0x0A;

(*XMSFunc)();

}



Two functions are now given: one for writing data to extended memory and one for reading data from extended memory into conventional memory:



/*

XMSParms is a structure for copying information to and from real-mode memory to XMS memory

*/



struct parmstruct

{

/*

blocklength is the size in bytes of block to copy

*/

unsigned long blockLength;



/*

sourceHandle is the XMS handle of source; 0 means that sourcePtr will be a 16:16 real-mode pointer, otherwise sourcePtr is a 32-bit offset from the beginning of the XMS area that sourceHandle points to

*/



unsigned int sourceHandle; far void *sourcePtr;



/*

destHandle is the XMS handle of destination; 0 means that destPtr will be a 16:16 real-mode pointer, otherwise

unsigned int destHandle; far void *destPtr;

}

XMSParms;



char XMS_write(unsigned long loc, char far *val, unsigned length)

{

/*

Round length up to next even value

*/

length += length % 2;

(loc);

/* Must be an even number! */



XMSParms.sourceHandle=0; XMSParms.sourcePtr=val; XMSParms.destHandle=XMSHandle; XMSParms.destPtr=(void far *) XMSParms.blockLength=length; _SI = FP_OFF(&XMSParms);

_AH=0x0B;

(*XMSFunc)();

if (_AX==0)

{

return 0;

}

return 1;

long loc,unsigned length)

}



oid *XMS_read(unsigned

{

/*

data

Returns pointer to or NULL on error

*/ /*

Round length up to next even value

*/

length += length % 2;



XMSParms.sourceHandle=XMSHandle; XMSParms.sourcePtr=(void far *) (loc); XMSParms.destHandle=0; XMSParms.destPtr=XMSBuf;

XMSParms.blockLength=length; /* Must be an even number

*/



_SI=FP_OFF(&XMSParms);

_AH=0x0B;

(*XMSFunc)();

if (_AX==0) return NULL;

}

return XMSBuf;

}

The following example puts the extended memory functions together:

/* A sequential table of variable length records in XMS */

#include #include #include #include #include

#define TRUE 1 #define FALSE 0



/*

BLOCKSIZE will be the size of our real -memory buffer that we'll swap XMS through (must be a multiple of 1024, since XMS is allocated in 1K chunks.)

*/



#ifdef __SMALL__

#define BLOCKSIZE (16L * 1024L) #endif



#ifdef __MEDIUM__ #define BLOCKSIZE (16L * 1024L)

#endif



#ifdef __COMPACT__ #define BLOCKSIZE (64L * 1024L)

#endif



#ifdef __LARGE__ #define BLOCKSIZE (64L * 1024L)

#endif



/*



XMSParms is a structure for copying information to and from real-mode memory to XMS memory

*/



struct parmstruct

{

/*

blocklength is the size in bytes of block to copy

*/

unsigned long blockLength;

sourceHandle is the XMS handle of source; 0 means that sourcePtr will be a 16:16 real-mode pointer, otherwise sourcePtr is a 32-bit offset from the beginning of the XMS area that sourceHandle points to

*/

unsigned int sourceHandle; far void *sourcePtr;



/*

destHandle is the XMS handle of destination; 0 means that destPtr will be a 16:16 real-mode pointer, otherwise destPtr is a 32-bit offset from the beginning of the XMS area that destHandle points to

*/



unsigned int destHandle; far void *destPtr;

}

XMSParms;

/* Used to call XMS manager (him



void far (*XMSFunc) (void); em.sys) */ char GetBuf(void); void GetXMSEntry(void);

char *XMSBuf; /* Conventional memory buffer for transfers */ unsigned int XMSHandle; /* handle to allocated XMS block */ char XMS_init()

{

/*

returns 0 if XMS present,

if XMS absent

if unable to allocate transfer buffer

*/

unsigned char status;

_AX=0x4300;



geninterrupt(0x2F); status = _AL; if(status==0x80) {

GetXMSEntry();

XMSBuf = (char far *) farmalloc(BLOCKSIZE); if (XMSBuf == NULL)

return 2; return 0;

}

return 1;

}

void GetXMSEntry(void)

GetXMSEntry sets XMSFunc to the XMS Manager entry point so we can call it later

*/

_AX=0x4310;

geninterrupt(0x2F);

XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);

}



void XMSSize(int *kbAvail, int *largestAvail)

{

/*

XMSSize returns the total kilobytes available, and the size in kilobytes of the largest available block

*/

_AH=8;

(*XMSFunc)();

*largestAvail=_DX;

*kbAvail=_AX;

}



char AllocXMS(unsigned long numberBytes)

{

/*

Allocate a block of XMS memory numberBytes long

*/

_DX = (int)(numberBytes / 1024); _AH = 9;

(*XMSFunc)();

if (_AX==0)

{



return FALSE;

}

XMSHandle=_DX; return TRUE;

}



void XMS_free(void)

{

/*

Free used XMS

*/

_DX=XMSHandle; _AH=0x0A;

(*XMSFunc)();

}



char XMS_write(unsigned long loc, char far *val, unsigned length)

{

Round length up to next even value

*/

length += length % 2;



XMSParms.sourceHandle=0; XMSParms.sourcePtr=val; XMSParms.destHandle=XMSHandle; XMSParms.destPtr=(void far *) (loc);

XMSParms.blockLength=length; /* Must be an even number! */

_SI = FP_OFF(&XMSParms);

_AH=0x0B; (*XMSFunc)(); if (_AX==0)

{

return FALSE;

}

return TRUE;

}



void *XMS_read(unsigned long loc,unsigned length)

{

/*

Returns pointer to data or NULL on error

*/ /*

Round length up to next even value

*/

length += length % 2;



XMSParms.sourceHandle=XMSHandle; XMSParms.sourcePtr=(void far *) (loc); XMSParms.destHandle=0; XMSParms.destPtr=XMSBuf;

XMSParms.blockLength=length; /* Must be an even number */

_SI=FP_OFF(&XMSParms); _AH=0x0B; (*XMSFunc)(); if (_AX==0)

{

return NULL;

}

return XMSBuf;

}

/*

Demonstration code

Read various length strings into a single XMS block (EMB) and write them out again

*/



int main()

int kbAvail,largestAvail; char buffer[80]; char *p; long pos; long end;



if (XMS_init() == 0)

printf("XMS Available ... \n"); else

{

printf("XMS Not Available\n");

return(1);

}



XMSSize(&kbAvail,&largestAvail); printf("Kilobytes Available: %d; Largest block: %dK\n",kbAvail,largestAvail);



if (!AllocXMS(2000 * 1024L)) return(1);



pos = 0;



do

{



p = fgets(buffer,1000,stdin)

if (p != NULL)

{

XMS_write(pos,buffer,strlen(buffer) + 1); pos += strlen(buffer) + 1;

}

}

while(p != NULL);



end = pos;



pos = 0;





{

memcpy(buffer,XMS_read(pos,100),70);

printf("%s",buffer);

pos += strlen(buffer) + 1;

}

while(pos < end);



/*

It is VERY important to free any XMS before exiting!

*/

XMS_free();

return 0;

No comments:

Post a Comment