CHAPTER 12:

LIBRARY FUNCTIONS

This chapter presents the functions in Small C's standard library. These functions exist as object modules which are automatically fetched by the linker when they are needed. All we have to do in our programs is call these functions when we require their services. The compiler automatically declares them to be external, and the linker automatically fetches them from the library and links them to our programs as it builds executable files.

We are free to define in our programs functions that have the same names as library functions. When we do, the defined functions override their library counterparts. The overridden library functions cannot be reached from such programs.

To enhance the portability of programs, C compilers (including Small C) implement a large subset of their library functions in a standard way--with the same names, arguments, return values, and functionality. However, some functions are unique to the particular compiler. Functions which are unique to Small C are identified below as Small C functions. Their use will detract from the portability of programs. However, since the source code for the library is available, we have the option of porting these unique functions together with our programs. In many cases no changes will be required. In other cases alterations may be necessary. And in some cases porting the functions will be out of the question; changing our programs would be easier.

The file STDIO.H should be included at the beginning of every program by writing the directive

	#include <stdio.h> 

This header file contains the definitions of a set of standard symbols which are used as file descriptors (below) and as values that are returned by the library functions. They are:

       #define stdin    0  /* fd for standard input file */
       #define stdout   1  /* fd for standard output file */
       #define stderr   2  /* fd for standard error file */
       #define stdaux   3  /* fd for standard comm port (COM1:) */
       #define stdprn   4  /* fd for standard printer port (LPT1:) */
       #define ERR     -2  /* error condition return value */
       #define EOF     -1  /* end of file return value */
       #define NULL     0  /* null value */

Also included in STDIO.H are some miscellaneous definitions which are handy to have around. They are:

       #define YES      1  /* Boolean true value */
       #define NO       0  /* Boolean false value */
       #define NULL     0  /* a null pointer or ASCII byte */
       #define CR      13  /* ASCII carriage return */
       #define LF      10  /* ASCII line feed */
       #define BELL     7  /* ASCII bell */
       #define SPACE  ' '  /* ASCII space */
       #define NEWLINE LF  /* the Small C newline character */

Input/Output Functions

The functions in this group give programs access to the outside world through the reading and writing of files. Even the keyboard, the screen, and devices connected to the serial and parallel ports are treated as files.

Small C differs somewhat from full C in the way it designates which file an I/O function is to reference. Full C compilers refer to I/O functions in two ways. Their high level I/O functions accept a pointer to a file-control structure as a means of identifying the pertinent file; each open file has such a structure. But their low level functions employ a small integer value, called a file descriptor (fd), instead. They have separate high and low level open functions that return a pointer or a file descriptor respectively for the newly opened file. The returned value is retained by the program and used thereafter to reference the file.

Small C, on the other hand, uses file descriptors exclusively. This may seem like a major difference, but in fact it is not very significant. It matters not whether the value returned by an open function is a pointer or integer. In either case, the program simply holds on to it, and passes it back to functions that expect it. When Small C programs are ported to full C compilers, their file descriptors may have to be redeclared as pointers. When both low and high level functions are used, it will be necessary to define pointers in addition to the existing file descriptors. Full C provides functions for mapping between pointers and file descriptors.

Five standard files are automatically opened and waiting for use whenever a Small C program begins execution. Their file descriptors have fixed values as defined by the first five #define directives above. Table 12-1 also shows their default open modes, and redirection capabilities. These files should be referenced by means of their standard names.

Table 12-1: Standard File-Descriptor Assignments

The standard input file (stdin) is automatically opened for reading and is assigned to the console keyboard by default. However, when the program is invoked, this assignment can be redirected from the keyboard to any other input device or file simply by placing a redirection specification (Chapter 15) in the command line. The advantage of this arrangement is obvious: it provides file independence. Programs can be written without reference to a specific input file or device name. They can then be associated with any given file at run time. If no redirection specification is given, stdin reads the keyboard.

The standard output file (stdout) is automatically opened for writing and is assigned to the console screen by default. This file, too, can be redirected at run time to any other output device or file (Chapter 15).

The standard error file (stderr) is also automatically opened for writing and is also assigned to the console screen. It differs from stdout, however, in that it cannot be redirected. This file is intended for the writing of error messages. By separating normal program output from error messages, only useful output data gets redirected with stdout while error messages continue to go to the screen. Of course, stderr is suitable for all kinds of operator communication, not just error messages.

These are the traditional standard files for C programs. However, since other MS-DOS C compilers implement standard files for the first serial and parallel ports, Small C does too. The standard auxiliary file (stdaux) is automatically opened for reading and writing, and is assigned to the first serial port (COM1:). And the standard printer file (stdprn) is automatically opened for writing and is assigned to the first parallel port (LPT1:).

Although three of the standard files cannot be redirected, they can be closed and reopened for reading and/or writing with assignment to any device or file--as can any file.

Lest I leave the impression that redirection is the only means of achieving file/device independence in C programs, let us note that it is not. As we shall see momentarily, arguments of any type (even file names) can be conveyed from the command line that invokes a program to the program itself. So there is no reason why file and device names cannot be supplied to programs through this means and used to open files. While this method does require the fetching of command line arguments and the opening of files, it is not limited to just one input and one output file as the redirection mechanism is.

When a file is opened, it receives the lowest unused file descriptor value. If the standard files are not closed, then other files receive file descriptors beginning at 5. A maximum of 20 files can be open simultaneously in Small C programs.

The data transfer functions in the Small C library fall into two groups. There are high level, character stream (ASCII) functions which incorporate the words get and put in their names, and there are the low level, binary functions that incorporate the words read and write. These groups of functions differ uniformly with respect to whether or not they process the data which passes through them. This processing is sometimes called cooking. As we might expect, uncooked data is said to be raw. The ASCII functions cook the data, whereas the binary functions do not. Small C does most of the cooking in the functions fgetc() and fputc(). Since the other get and put functions call these functions, they also cook their data. The cooking process differs according to whether the data transfer is in or out. On input, the following actions occur:

  1. If the file is assigned to the keyboard, the input characters are echoed to the screen. If the character is a carriage return it is echoed as a two character sequence--carriage return, line feed.
  2. If the file is assigned to the keyboard and the character is a control-C, the program immediately terminates execution with an exit code of 2. This code can be tested in batch files. The control-C character does not echo.
  3. If the file is assigned to the keyboard and the character is an ASCII DEL (0x7F) it is changed to a backspace. Therefore, both the backspace and delete keys cause a rubout of the last character when input is through the string input functions.
  4. Regardless of the source of the data, if the character is a line feed it is discarded, and if it is a carriage return it is changed to the value of the Small C newline character ('\n'). The effect is to represent the end of a line with a single newline character.
  5. Regardless of the source of the data, if the character is a control-Z the end-of-file status is set and the character is changed to EOF as defined in STDIO.H.

On output, the following occurs:

  1. If the character has the value EOF, it is changed to the DOS end-of-file character--control-Z.
  2. If the character is the newline character ('\n') it is written as the sequence carriage return, line feed.

Notice how the end of a line is handled. While it may be represented externally by two characters, internally it is represented by just one character with the value of the character constant '\n' (Chapter 3). This two-to-one mapping is a standard feature of ASCII I/O transfers in the C language. Although the value of '\n' is defined by NEWLINE in the header file STDIO.H, we should always use the escape sequence rather than NEWLINE or its value when testing for the end of a line. Not all C compilers assign the same value to '\n' and none of them define NEWLINE in their STDIO.H files.

The backspace and control-X characters have special meanings to the string input functions fgets() and gets(). If input is from the keyboard, a backspace (recall that DEL becomes a backspace) causes these functions to rubout the last character received. In so doing they echo a backspace, space, backspace sequence to the screen to erase the previous character and reposition the cursor over it. Furthermore, a control-X from the keyboard causes them to wipeout the entire line in a similar manner. This allows the operator to restart his input from scratch. These functions are also sensitive to EOF (externally control-Z) as the end-of-file indicator. Upon receipt of this value, they cease input, terminate the input string with a null byte, and return to the user.

Remember that only the get and put functions cook the data. The read and write functions simply transfer it without change. In addition, there is a special function called poll() which polls the keyboard without waiting for a key to be struck.

This scheme of dividing the data transfer functions into ASCII and binary groups is unique to Small C. Full C compilers do this by providing open modes that indicate whether or not cooking is to be done. When porting Small C programs to full C compilers, the new open modes must specify the same actions as the Small C functions.

We now look at the I/O functions individually.

fopen (name, mode) char *name, *mode;

This function attempts to open the file named by the character string pointed to by name. Name is an ordinary MS-DOS file specification, possibly including drive, path, filename, and extension. Only the filename is required. The drive and path will assume the defaults as established by MS-DOS. Obviously, the wild card characters (? and *) are not allowed.

Mode points to a string indicating the use for which the file is being opened. The following values are accepted:

		"r"	read

"w" write

"a" append

Read mode opens an existing file for input. Write mode creates a new file. If a file with the same name already exists, it is replaced by the new file. Since the new file starts out empty, writing begins with the first byte. Append mode allows writing which begins at the end of an existing file or the beginning of a new one. If a file with the same name already exists, it is opened and positioned after the last byte. If no file with the specified name is found, one is created.

In addition, there are variations on these three modes which permit both reading and writing (updating). They are:

		"r+"	update read

"w+" update write

"a+" update append

In terms of their effects at open time, these modes are the same as their non-update counterparts; however, these modes support both input and output operations. We can freely mix them in any order that we find useful. Each file has a current position from which the next read or write operation begins. After each operation, the current position is set to the byte following the last one transferred. Therefore, successive I/O operations reference successive byte strings in the file. This sequential behavior can be altered by calls to functions which directly alter the current position to provide a random accessing capabilities.

If the attempt to open a file is successful, fopen() returns an fd value for the open file; otherwise, it returns NULL. The fd should be kept for use with subsequent I/O function calls. A typical statement that opens a file might look like

  if(!(data_fd = fopen("data.fil", "r"))) error("can't open: data.fil"); 

where error() is some error handling function in the program. Only the standard files may be used without first calling fopen().

fclose (fd) int fd;

This function closes the specified file. Since Small C buffers disk file data, when a disk file (opened for writing or updating) is closed, its buffer is flushed to the disk if it contains pending data. This function returns NULL if the file closes successfully, and ERR if an error occurs.

When a program exits, either by reaching the end of main() or by calling exit() or abort(), all open files are closed automatically. However when a program dies a hard death (loss of system power or rebooting the system), buffered data may be lost.

freopen (name, mode, fd) char *name, *mode; int fd;

This function closes the previously opened file indicated by fd and opens a new one whose name is in the character string at name. Mode specifies the new open mode just as for fopen(). On success, this function returns the original value of fd. On failure, however, it returns NULL.

Note that since the fd for the standard input file is zero there is no way of distinguishing success from failure in that case. It is best to simply leave stdin open even if it will not be used.

getc (fd) int fd;

Same as fgetc().

fgetc (fd) int fd;

This function returns the next character from the file indicated by fd. If no more characters remain in the file, or an error condition occurs, it returns EOF. The end of the file is detected by an occurrence of the end-of-file character (control-Z) or the physical end of the file.

getchar ()

This function is equivalent to the call fgetc(stdin). It presumes to use the standard input file.

fgets (str, sz, fd) char *str; int sz, fd;

This function reads up to sz-1 characters into memory from the file indicated by fd. The target buffer in memory is indicated by str. Input terminates after transferring a newline character. A null character is appended behind the newline. If no newline is found after transferring sz-1 characters input also terminates and a null character is appended after the last character transferred. Fgets() returns str if the operation is successful. If the end of the file is reached while attempting to obtain another character, or if an error occurs, it returns NULL.

ungetc (c, fd) char c; int fd;

This function logically (not physically) pushes the character c back into the input file indicated by fd. The next read from the file will retrieve the ungotten character first. Only one character at a time (per file) may be kept waiting in this way. This function returns the character itself on success, or EOF if a previously pushed character is being held or if c has the value EOF. We cannot push EOF back into a file. Performing a seek or rewind operation on a file causes the ungotten character to be forgotten.

fread (ptr, sz, cnt, fd) char *ptr; int sz, cnt, fd;

This function reads, from the file indicated by fd, cnt items of data of length sz. The target buffer is indicated by ptr. A binary transfer is performed and only the physical end of the file is recognized. A count of the actual number of items read is returned to the caller. This could be less than cnt if the end of the file is reached. This is the usual way to tell when the end of the file is reached. However, we may call feof() to determine when the data has been exhausted, and ferror() to detect errors.

read (fd, ptr, cnt) int fd, cnt; char *ptr;

This function reads, from the file indicated by fd, cnt bytes of data into memory at the address indicated by ptr. This function performs a binary transfer and recognizes only the physical end of the file. A count of the actual number of bytes read is returned to the caller. This could be less than cnt if the end of the file was encountered. This is the usual way of telling when the end of the file is reached. However, we may call feof() to determine for certain when the data is exhausted, and ferror() to detect errors.

gets (str) char *str;

This function reads characters from stdin into memory starting at the address indicated by str. Input is terminated at the end of a line, but the newline character is not transferred. A null character terminates the input string. Gets() returns str for success; otherwise, it returns NULL for end-of-file or an error. Since this function has no way of knowing the size of the destination buffer, it is possible that an input line might overrun the allotted space. We must check the size of the input string to verify that it was not too large.

putc (c, fd) char c; int fd;

Same as fputc().

fputc (c, fd) char c; int fd;

This function writes the character c to the file indicated by fd. It returns the character itself on success; otherwise, EOF.

putchar (c) char c;

This function is equivalent to the call fputc(c, stdout). It presumes to use the standard output file.

fputs (str, fd) char *str; int fd;

This function writes a string of characters, beginning at the address indicated by str, to the file indicated by fd. Successive characters are written until a null byte is found. The null byte is not written and a newline character is not appended to the output. It is the programmer's responsibility to see that the necessary newline characters are in the string itself.

puts (str) char *str;

This function is almost like the call fputs(str, stdout). It presumes to write to the standard output file. Unlike fputs(), however, it appends a newline to the output.

fwrite (ptr, sz, cnt, fd) char *ptr; int sz, cnt, fd;

This function writes, to the file indicated by fd, cnt items of sz bytes from the memory buffer indicated by ptr. It returns a count of the number of items written. An error condition may cause the number of items written to be less than cnt. Ferror() should be called to verify error conditions, however. This function performs a binary transfer.

write (fd, ptr, cnt) int fd, cnt; char *ptr;

This function writes, to the file indicated by fd, cnt bytes from the memory buffer indicated by ptr. It returns a count of the number of bytes written. An error condition may cause the number of items written to be less than cnt. Ferror() should be called to verify error conditions, however. This function performs a binary transfer.

printf (str, arg1, arg2, ...) char *str;

This function writes to the standard output file a formatted string which is the string at str with the ASCII equivalents of arg1, arg2, ... inserted at specified points.

It returns a count of the total number of characters written. The string at str is called the control string. The control string is required, but the other arguments are optional. The control string contains ordinary characters and groups of characters called conversion specifications. Each conversion specification tells printf() how to convert its corresponding argument into an ASCII string for output. The converted argument replaces its conversion specification in the output. The character % signals the start of a conversion specification and one of the letters b, c, d, o, s, u, or x terminates it.

Between the start and end of a conversion specification, the following optional fields may be found (in the order listed, without intervening blanks):

  1. a minus sign (-),
  2. a decimal integer constant (nnn), and/or
  3. a decimal fraction (.mmm).

These subfields are all optional. In fact, one frequently sees conversion specifications without them. The minus sign indicates that the string, produced by applying a specified conversion to its argument, is to be left adjusted in its output field. The decimal integer indicates the minimum width of the output field (in characters). If more space is needed it will be used, but at least the number of positions indicated will be generated. The decimal fraction is used where the argument is itself a character string (more correctly, the address of a character string). In this case the fraction indicates the maximum number of characters to take from the string. If there is no fraction in the specification, then all of the string is used.

The terminating letter indicates the type of conversion to be applied to the argument. It may be one of the following:

b -- The argument should be considered an unsigned integer and converted to binary format for output. No leading zeroes are generated. This specification is unique to Small C.

c -- The argument should be output as a character without conversion. The high-order byte is to be ignored.

d -- The argument should be considered a signed integer and converted to a (possibly signed) decimal digit string for output. No leading zeroes are generated. The left most character is reserved for the sign--blank if positive, hyphen if negative.

o -- The argument should be considered an unsigned integer and converted to octal for output. No leading zeroes are generated.

s -- The argument is the address of a string which should be output according to the justification, minimum width, and maximum size specifications indicated.

u -- The argument should be considered an unsigned integer and converted to unsigned decimal for output. No leading zeroes are generated.

x -- The argument should be considered an unsigned integer and converted to hexadecimal for output. No leading zeroes are generated. If a % is followed by anything other than a valid specification, it is ignored and the following character is written without change. Thus %% writes %.

Printf() scans the control string from left to right, sending everything to stdout until it finds a %. It then evaluatesthe conversion specification and applies it to the first argument (following the control string). The resulting string is written to stdout. It then resumes writing from the control string until it finds the next conversion specification which it applies to the second argument. The continues until the control string is exhausted. The result is a formatted output message consisting of both literal and variable data. See Table 12-2 for examples.

Table 12-2: Printf Examples

fprintf (fd, str, arg1, arg2, ...) int fd; char *str;

This function works exactly like printf() except that output goes to the file indicated by fd instead of stdout.

scanf (str, arg1, arg2, ...) char *str;

This function reads a series of fields from the standard input file, converts them to internal format according to conversion specifications contained in the control string str, and stores them at the locations indicated by the arguments arg1,arg2,... .

It returns a count of the number of fields read. A field in the input stream is a contiguous string of graphic characters. It ends with the next white space (blank, tab, or newline) or, if its conversion specification indicates a maximum field width (below), it ends when the field width is exhausted. A field normally begins with the first graphic character after the previous field; that is, leading white space is skipped. Since the newline character is skipped while searching for the next field, scanf() reads as many input lines as necessary to satisfy the conversion specifications in the control string. Each of the arguments following the control string must yield an address value.

The control string contains conversion specifications and white space (which is ignored). Each conversion specification informs scanf() how to convert the corresponding field into internal format, and each argument following str gives the address where the corresponding converted field is to be stored. The character % signals the start of a conversion specification and one of the letters b, c, d, o, s, u, or x ends it.

Between these may be found, with no intervening blanks, an asterisk and/or a decimal integer constant. These subfields are both optional. In fact, conversion specifications are frequently written without them.

The asterisk indicates that the corresponding field in the input stream is to be skipped. Skip specifications do not have corresponding arguments.

The numeric field indicates the maximum field width in characters. If present, it causes the field to be terminated when the indicated number of characters has been scanned, even if no white space is found. However, if white space is found before the field width is exhausted, the field is terminated at that point.

The terminating letter indicates the type of conversion to be applied to the field. It may be one of the following:

b -- The field should be considered a binary integer and converted to an integer value. The corresponding argument should be an integer address. Leading zeroes are ignored. This specification is unique to Small C.

c -- The field should be accepted as a single character without conversion. This specification inhibits the normal skip over white space; blanks are transferred just like other characters. The argument for such a field should be a character address.

d -- The input field should be considered a (possibly signed) decimal integer and converted into an integer value. The corresponding argument should be an integer address. Leading zeroes are ignored.

o -- The field should be considered an unsigned octal integer and converted to an integer value. The corresponding argument should be an integer address. Leading zeroes are ignored.

s -- The field should be considered a character string and stored with a null terminator at the character address indicated by its argument. There must be enough space at that address to hold the string and its terminator. Remember, a maximum field width can be specified to prevent overflow. The specification %1s will read one character. It differs from %c in that it skips white space, whereas the latter reads the next character, whatever it is.

u -- The field should be considered an unsigned decimal integer and converted to an integer value. The corresponding argument should be an integer address. Leading zeroes are ignored. This specification is unique to Small C.

x -- The field should be considered an unsigned hexadecimal number and converted to an integer value. The corresponding argument should be an integer address. Leading zeroes or a leading 0x or 0X will be ignored.

Scanf() scans the control string from left to right, processing input fields until the control string is exhausted or a field is found which does not match its conversion specification. If the value returned by scanf() is less than the number of conversion specifications, an error has occurred or the end of the input file has been reached. EOF is returned if no fields are processed because the end of the file has been reached.

If the statement

	scanf("%s %c %c %*s %d %3d %d", str, &c1, &c2, &i1, &i2, &i3);

is executed when the input stream contains

	"abc defg    -12   345678 9" 

the values stored would be:

		"abc\0"	at str

' ' in c1

'd' in c2

-12 in i1

345 in i2

678 in i3

Future input from the file would begin with the space following 345678.

fscanf (fd, str, arg1, arg2, ...) int fd; char *str;

This function works like scanf() except that the input is taken from the file indicated by fd, instead of stdin.

rewind (fd) int fd;

This function positions the file indicated by fd to its beginning. It is equivalent to a seek to the first byte of the file. It returns NULL on success; otherwise, EOF.

bseek (fd, offset, from) int fd, offset[], from;

This Small C function positions the MS-DOS file pointer for fd to the byte indicated by a double length integer offset. Offset must be given as the address of an integer array of two elements such that offset[0] is the low-order half and offset[1] is the high-order half. From determines the base location from which the offset is applied. It may have one of the values:

		0	beginning of file

1 current byte in file

2 end of file

The last case should be used with a minus offset. Bseek() returns NULL on success; otherwise, EOF. It works like the UNIX fseek() except that the offset is an address instead of the actual value. This is necessary since Small C does not support long variables.

btell (fd, offset) int fd, offset[];

This Small C function returns the offset to the current byte in the file indicated by fd. The current byte is the next one that will be read from or written to the file. No account of ungetc() calls is taken. Offset is a double length integer and must be specified as the address of an integer array of two elements such that offset[0] is the low-order half and offset[1] is the high-order half. The array at offset receives the offset value of the current byte in the file. Btell() returns NULL on success; otherwise, ERR.

cseek (fd, offset, from) int fd, offset, from;

This Small C function positions the file indicated by fd to the beginning of the 128-byte block which is offset positions from the first block, current block, or end of the file depending on whether from is 0, 1, or 2 respectively. Subsequent reads and writes proceed from that point. Cseek() returns NULL for success; otherwise, EOF.

cseekc (fd, offset) int fd, offset;

This Small C function positions fd on the character indicated by offset within the current 128-byte block. The current byte must be on a block boundary when this function is called. Used in combination with cseek() this function allows us to seek to any given byte in a file by tracking file positions in two parts--the block offset and the byte offset within the block. Cseekc() returns NULL on success; otherwise, EOF.

ctell (fd) int fd;

This Small C function returns the position of the current block of the file indicated by fd. The returned value is the offset of the current 128-byte block with respect to the beginning of the file. If fd is not assigned to a disk file, ERR is returned.

ctellc (fd) int fd;

This Small C function returns the offset (0-127) to the current byte in the current block for fd. The current byte is the next one that will be read from or written to the file. No account of ungetc() calls is taken.

rename (old, new) char *old, *new;

This Small C function changes the name of the file specified by old to the name indicated by new. It returns NULL on success; otherwise, ERR.

delete (name) char *name;

Same as unlink().

unlink (name) char *name;

This function deletes the file indicated by the character string at name. It returns NULL on success; otherwise, ERR.

feof (fd) int fd;

This function returns true if the file designated by fd has reached its end. Otherwise, it returns false.

ferror (fd) int fd;

This function returns true if the file designated by fd has encountered an error condition since it was last opened. Otherwise, it returns false.

clearerr (fd) int fd;

This function clears the error status for the file indicated by fd.

iscons (fd) int fd;

This Small C function returns true if fd is assigned to the console; otherwise, false.

isatty (fd) int fd;

This function returns true if fd is assigned to a device rather than a disk file; otherwise, false.

auxbuf (fd, size) int fd, size;

This Small C function allocates an auxiliary buffer of size bytes for use with fd. It returns zero for success and ERR if it fails. Fd may or may not be open. Size must be greater than zero and less than the amount of free memory. If fd is a device, the buffer is allocated, but ignored. This implementation of Small C uses file handle type MS-DOS calls; it reads and writes in chunks the size of the auxiliary buffer. Extra buffering is useful in reducing disk head movement and drive switching during sequential operations. Once an auxiliary buffer is allocated it remains for the duration of program execution, even if fd is closed. Calling this function a second time for fd is allowed; however, the original buffer continues to occupy space in memory. Alternating read and write operations and performing seeks with auxiliary buffering is also allowed. Ungetc() will operate normally. Ordinarily, it is unnecessary and wasteful to alloc ate auxiliary buffers to both input and output files.

Format Conversion Functions

atoi (str) char *str;

This function converts the decimal number in the string at str to an integer, and returns its value. Leading white space is skipped and an optional sign (+ or -) may precede the left-most digit. The first non-numeric character terminates the conversion.

atoib (str, base) char *str; int base;

This Small C function converts the unsigned integer of base base in the string at str to an integer, and returns its value. Leading white space is skipped. The first non-numeric character terminates the conversion.

itoa (nbr, str) int nbr; char *str;

This function converts the number nbr to its decimal string representation at str. The result is left justified at str with a leading minus sign if nbr is negative. A null character terminates the string which must be large enough to hold the result.

itoab (nbr, str, base) int nbr; char *str; int base;

This Small C function converts the unsigned integer nbr to its string representation at str in base base. The result is left justified at str. A null character terminates the string which must be large enough to hold the result.

dtoi (str, nbr) char *str; int *nbr;

This Small C function converts the (possibly signed) decimal number in the string at str to an integer at nbr and returns the length of the numeric field found. The conversion stops when the end of the string or a non-decimal character is reached. Dtoi() will use a leading sign and at most five digits. Dtoi() returns ERR if the absolute value of the number exceeds 32767.

otoi (str, nbr) char *str; int *nbr;

This Small C function converts the octal number in the string at str to an integer at nbr and returns the length of the octal field. The conversion stops when the end of the string or a non-octal character is reached. Otoi() will use at most six digits. A number larger than 177777 will cause otoi() to return ERR.

utoi (str, nbr) char *str; int *nbr;

This Small C function converts the unsigned decimal number in the string at str to an integer at nbr and returns the length of the numeric field. The conversion stops when the end of the string or a non-decimal character is reached. Utoi() will use at most five digits. A number larger than 65535 will cause utoi() to return ERR.

xtoi (str, nbr) char *str; int *nbr;

This Small C function converts the hexadecimal number in the string at str to an integer at nbr and returns the length of the hexadecimal field. The conversion stops when the end of the string or a non-hexadecimal character is reached. Xtoi() will use at most four digits. If more hex digits are present, it returns ERR.

itod (nbr, str, sz) int nbr, sz; char *str;

This Small C function converts nbr to a (possibly signed) signed character string at str. The result is right justified and blank filled in str. The sign and possibly high-order digits are truncated if the destination string is too small. It returns str. Sz indicates the length of the string. If sz is greater than zero, a null byte is placed at str[sz-1]. If sz is zero, a search for the first null byte following str locates the end of the string. If sz is less than zero, all sz characters of str are used including the last one.

itoo (nbr, str, sz) int nbr, sz; char *str;

This Small C function converts nbr to an octal character string at str. The result is right justified and blank filled in the destination string. High-order digits are truncated if the destination string is too small. It returns str. Sz indicates the length of the string. If sz is greater than zero, a null byte is placed at str[sz-1]. If sz is zero, a search for the first null byte following str locates the end of the string. If sz is less than zero, all sz characters of str are used including the last one.

itou (nbr, str, sz) int nbr, sz; char *str;

This Small C function converts nbr to an unsigned decimal character string at str. It works like itod() except that the high-order bit of nbr is taken as a magnitude bit.

itox (nbr, str, sz) int nbr, sz; char *str;

This Small C function converts nbr to a hexadecimal character string at str. The result is right justified and blank filled in the destination string. High-order digits are truncated if the destination string is too small. It returns str. Sz indicates the length of the string. If sz is greater than zero, a null byte is placed at str[sz-1]. If sz is zero, a search for the first null byte following str locates the end of the string. If sz is less than zero, all sz characters of str are used including the last one.

String Manipulation Functions

left (str) char *str;

This Small C function left adjusts the character string at str. Starting with the first non-blank character and proceeding through the null terminator, the string is moved to the address indicated by str. This function can be used to left adjust the output of the ito?() functions above. For example,

		left(itod(i, str, 6)); 

will convert i to a decimal string which is left adjusted in str.

strcat (dest, sour) char *dest, *sour;

This function appends the string at sour to the end of the string at dest. The null character at the end of dest is replaced by the leading character of sour. A null character terminates the new dest string. The space reserved for dest must be large enough to hold the result. This function returns dest.

strncat (dest, sour, n) char *dest, *sour; int n;

This function works like strcat() except that a maximum of n characters from the source string will be transferred to the destination string.

strcmp (str1, str2) char *str1, *str2;

This function returns an integer less than, equal to, or greater than zero depending on whether the string at str1 is less than, equal to, or greater than the string at str2. The strings are compared left to right, character by character, until a difference is found or they end simultaneously. Comparison is based on the numeric values of the characters. Str1 is considered less than str2 if str1 is equal to but shorter than str2, and vice versa.

strncmp (str1, str2, n) char *str1, *str2; int n;

This function works like strcmp() except that a maximum of n characters are compared.

strcpy (dest, sour) char *dest, *sour;

This function copies the string at sour to dest. Dest is returned. The space at dest must be large enough to hold the string at sour. A null character follows the last character placed in the destination string.

strncpy (dest, sour, n) char *dest, *sour; int n;

This function works like strcpy() except that n characters are placed in the destination string regardless of the length of the source string. If the source string is too short, null padding occurs. If it is too long, it is truncated in dest. A null character follows the last character placed in the destination string.

strlen (str) char *str;

This function returns a count of the number of characters in the string at str. It does not count the null character that terminates the string. Since the length of a string can be found only by scanning it from beginning to end, this function can be time consuming. To minimize this overhead, the current Small C library implements this function in assembly language with a repeat prefix attached to the 8086 string-compare instruction for the fastest possible speed.

strchr (str, c) char *str, c;

This function returns a pointer to the first occurrence of the character c in the string at str. It returns NULL if the character is not found. Searching ends at the null terminator.

strrchr (str, c) char *str, c;

This function works like strchr() except that the right-most occurrence of the character is sought.

reverse (str) char *str;

This function reverses the order of the characters in the null terminated string at str.

pad (str, ch, n) char *str, ch; int n;

This Small C function fills the string at str with n occurrences of the character ch.

Character Classification Functions

The following functions determine whether or not a character belongs to a designated class of characters. They return true if it does and false if not. Since these functions are identical except for the condition for which they test, they are simply listed below with their conditions:

Except for isascii(), only characters in the ASCII set (0-127) should be tested. Characters greater than 127 will yield unpredictable answers.

Character Translation Functions

toascii (c) char c;

This function returns the ASCII equivalent of c. Since MS-DOS systems use the ASCII character set, it merely returns c unchanged. This function makes it possible to use the properties of the ASCII code set without introducing implementation dependencies into programs.

tolower (c) char c;

This function returns the lowercase equivalent of c if c is an uppercase letter; otherwise, it returns c unchanged.

toupper (c) char c;

This function returns the uppercase equivalent of c if c is a lowercase letter; otherwise, it returns c unchanged.

Lexicographical Comparison Functions

lexcmp (str1, str2) char *str1, *str2;

This Small C function works like strcmp() except that a lexicographical comparison is used. For meaningful results, only characters in the ASCII character set (codes 0-127) should appear in the strings. Alphabetic characters are compared without case sensitivity; i.e., uppercase and lowercase letters are equivalent. Overall, the sequence is: (1) control characters, (2) special characters (in ASCII order), (3) numerics, (4) alphabetics, and (5) the delete character (DEL).

lexorder (c1,c2) char c1, c2;

This Small C function returns an integer less than, equal to, or greater than zero depending on whether c1 is less than, equal to, or greater than c2 lexicographically. For meaningful results, only characters in the ASCII character set (codes 0-127) should be passed. Alphabetic characters are compared without case sensitivity; i.e., uppercase and lowercase letters are equivalent. Overall, the sequence is: (1) control characters, (2) special characters (in ASCII order), (3) numerics, (4) alphabetics, and (5) the delete character (DEL).

Mathematical Functions

abs (nbr) int nbr;

This function returns the absolute value of nbr.

sign (nbr) int nbr;

This function returns -1, 0, or +1 depending on whether nbr is less than, equal to, or greater than zero respectively.

Program Control Functions

avail (abort) int abort;

This Small C function returns the number of bytes of free memory which exists between the memory heap and the stack. It also checks to see if the stack overlaps the heap; if so and if abort is not zero, the program is aborted with an exit code of 1. However, if abort is zero, avail() returns zero to the caller. This function makes it possible to make full use of the data segment. However, care should be taken to leave enough space for the stack to grow.

calloc (nbr, sz) int nbr, sz;

This function allocates nbr*sz bytes of zeroed memory. It returns the address of the allocated memory block. If insufficient memory exists, the request fails and zero is returned. If the stack and the heap are found to overlap, then the program aborts with an exit code of 1.

malloc (nbr) int nbr;

This function allocates nbr bytes of uninitialized memory. It returns the address of the allocated memory block. If insufficient memory is available, the request fails and zero is returned. If the stack and the heap are found to overlap, then the program aborts with an exit code of 1.

free (addr) char *addr;

Same as cfree().

cfree (addr) char *addr;

This function releases a block of previously allocated heap memory beginning at addr. It returns addr on success, otherwise NULL.

Small C uses a simplified memory allocation scheme. It allocates memory in a heap that starts after the last global item in the data segment and expands in the direction of the stack. Furthermore, it does not keep track of memory which has already been allocated; it only maintains a single pointer to the end of the heap. Because of this, blocks of allocated memory must be freed in the reverse order from which they were allocated.

Since the open functions allocate buffer space on the heap and the close function does not free it, freeing memory which was allocated before opening a file must be avoided.

getarg (nbr, str, sz, argc, argv) char *str; int nbr, sz, argc, *argv;

This Small C function locates the command-line argument indicated by nbr, moves it (null terminated) into the string at str (maximum size sz), and returns the length of the field obtained. Argc and argv must be the same values provided to main() when the program is started. If nbr is one the first argument (following the program name) is requested, if two the second argument is requested, and so on. If there is no argument for nbr, getarg() puts a null byte at str and returns EOF.

poll (pause) int pause;

This Small C function polls the keyboard for operator input. If no input is waiting, zero is returned. If a character is waiting, the value of pause determines what happens. If pause is zero the character is returned immediately. If pause is not zero and the character is a control-S, there is a pause in program execution; when the next character is entered, zero is returned. If the character is a control-C, program execution is terminated with an exit code of 2. All other characters are returned to the caller immediately. Poll() calls _getkey() and so translates auxiliary keystrokes accordingly (Table 12-3).

abort (errcode) int errcode;

Same as exit().

exit (errcode) int errcode;

This function closes all open files and returns to MS-DOS. The value of errcode determines whether or not a normal exit is to be taken. If it is not zero, then the message

		Exit Code: n 

(where n is the decimal value of errcode) is displayed on the screen before control returns to MS-DOS. In either case, errcode is returned to MS-DOS for checking in batch files.

Primitive Functions

The following functions are not normally needed in programs. But they exist in the standard Small C library and are documented here in case they may be of use. Since they are in the module CSYSLIB, which is linked with every Small C program, there is no incremental memory cost associated with their use.

_hitkey()

This Small C function calls the Basic I/O System (BIOS) to test the keyboard for a pending keystroke. It returns true or false according to whether or not a key has been hit.

_getkey()

This Small C function calls the BIOS to return the next byte from the keyboard. It returns immediately if a keystroke is pending; otherwise, it waits for one. By testing the keyboard with _hitkey() before calling this function, we can avoid blocking the program until a key is pressed. This is how poll() obtains its input. All input from the keyboard, regardless of the input function, is obtained through this function.

Most keystrokes have obvious ASCII values. However, there are numerous auxiliary codes that can be generated through the use of special keys and various key combinations. The BIOS assigns values to auxiliary keystrokes which overlap the ASCII codes. To distinguish between these, _getkey() adds an offset of 113 (decimal) to the BIOS's auxiliary codes, placing them in the range 128-245. The only exception is the traditional (with ASCII terminals) key combination control-@ which usually generates a value of zero. The BIOS's auxiliary code for this (and the unshifted ctrl-2) is 3 which _getkey() translates directly to zero. Table 12-3 lists the special keystrokes and the values transmitted through the standard Small C input functions.

Table 12-3: Small C Codes for Auxiliary Keystrokes

_bdos2(ax, bx, cx, dx) int ax, bx, cx, dx;

This Small C function provides a general means of making interrupt-21 calls to the Basic Disk Operating System (BDOS). The digit 2 in its name signifies that _bdos2() is designed for use with the new, version 2.0 MS-DOS services. Except for the keyboard servicing functions above, all other interactions with a Small C program's environment pass through this function.

This function takes four 16-bit arguments--the values to place in the AX, BX, CX, and DX registers before issuing the interrupt. On return, if there was no error, zero is returned. If something went wrong, however, the returned value contains the standard MS-DOS error code.

Since MS-DOS draws attention to the its error code by setting the carry flag (apparently not consistently though) this function also passes on that information by negating the error codes. If the carry flag is set, indicating an error, the error codes are subtracted from zero to form a negative value (two's complement of the original). So, if we want to trust MS-DOS's carry flag error indicator, we can just test for a value less than zero. On the other hand, a more dependable test for errors is any non-zero value. If we want to analyze the specific error, then we must negate the code again if it is negative.

Refer to other material for details on the BDOS calls and the standard error codes. Peter Norton[15] and Ray Duncan[16] are good sources for this information.

Go to Chapter 13 Return to Table of Contents