[html,12pt,a4]article C [1]#1 safe-c [2]#1#2 [1]"#1" htmlonly [1]"#1" htmlonly The User Guide Barry McMullin (Last Revised: 12th January 1996) Introduction is a module for safe (or, at least, safer) programming in C. It provides a collection of functions, with related data types and constants. These permit the development of basic programs, without recourse to the standard library. Instead, provides an insulating layer between the novice programmer and the standard library functions. adds a variety of additional runtime checks to guard against typical novice errors, and provide intelligible runtime failure reports. is not intended to be applied in production software. It is strictly a pedagogical stepping-stone, to be displaced by direct use of the standard library when the student is ready. This document provides a User Guide to . Specifically, it describes each constant, data type, and function, provided by . It does not provide any information on the rationale for the particular features included in , nor does it discuss the internal architecture or implementation of . What is a Module anyway? A module normally consists of three separate files: A header file, normally having the extension .h . This provides a specification of the external interface to the module. That is, it specifies exactly how other modules can invoke or use the facilities of this module. A source file, normally having the extension .c . This is the actual source code, or definition, of the module. It specifies the internal implementation of the module. An object file, normally having the extension .o . This is the file produced by using the compiler to translate the source code in the source file into the specific, binary, instruction codes (so-called machine code) which can be directly processed by the CPU in the computer on which programs will be executed. A user of a module, who wishes to use its facilities in her programs, needs access to the header file and the object file. The header file is necessary so that, while the compiler is translating your source code, it can check that it is interfacing in a correct and valid manner with the external module. The object file is necessary so that a complete executable file can be created by combining or linking together the object code from your source file, with the object code of the external module. The user of a module does not require access to the source code of that module. The source code is only required if someone wants to modify the module in some way - correct some defect, or add new facilities etc. Using As with any free-standing module, two separate issues or problems arise in using the module: How can the compiler discover, or be informed about, the facilities offered by (the interface to it)? This is necessary because, in order to compile your code, which is supposed to mesh with the code, the compiler must know what sort of hooks or connections are available in the code. How can the compiler know, or be told, to combine the object code (compiled) version of the module, with the compiled version of your code, in order to produce a complete, executable, file? The first problem, of telling the compiler what the allowed interface to is, is solved by inserting the following preprocessor directive close to the top of your source file, before any other reference to entities: The effect of this directive is that, during the compilation, the file safe-c.h is searched for in certain directories. Assuming it is found, then its text is incorporated into the compilation exactly as it it had been part of your source file. The file safe-c.h is the header file for the module and, as such, contains declarations of all the entities (constants, data types, functions) provided by . Thus, when the compiler subsequently encounters references to these entities in your code, it can recognise or decide whether they are valid. For example, safe-c.h contains a prototype for each function. This warns the compiler of the existence, in the module, of the function, and also specifies how many arguments (if any), of what types, the function can accept. Then when the compiler encounters an attempt to invoke the function, it can check whether the correct number of arguments, of the correct types, are present. It might seem that the second problem - that of subsequently finding the compiled version of and combining it with the compiled version of your own file, in order to produce the complete executable program - simply should not arise. Surely, if the compiler is told, by the inclusion of safe-c.h, that your program makes use of facilities of , that should prompt the compiler to also look for the compiled version of , and incorporate it into the executable file? However, few things in computer programming are as simple as they seem. The include directive specifies that the text of another file must be incorporated into the translation phase of the compile - i.e. while translating your source file into object code. It does not stipulate anything about what object files should subsequently be linked together to create the executable file. Indeed, it must not. This is because, in the general case, there is no simple or necessary one-to-one relationship between header files and object files. So, in the general case, stipulations of what header files to include during translation, and what object files to combine during linkage, must be kept separate. Specifying to the compiler to include the object file in the linkage phase simply requires an additional parameter to be placed in the compiler command line. However: there are a couple of other special stipulations that also have to be made to the compiler, and also a little postprocessing/tidying up to be done after the compilation. Therefore, is accompanied by a small batch file called scc.bat. A batch file is simply a text file containing a series of DOS commands, to be executed in sequence. So, if your file is called, say, hello.c, then the command: will actually cause a whole series of commands in scc.bat to be executed. These will automatically invoke the compiler in the correct way to incorporate the object file into the executable file. However: note that scc.bat is only appropriate for compiling programs which make use of . When you progress to the point of directly using the standard library, then you must also learn how to directly invoke the compiler in the appropriate way, rather than relying on scc.bat to do it. Overview introduces three distinct data types. A data type is a particular format for storing information of some particular kind. The three data types are: booleantype: This only has two possible values, denoted by the symbolic constants TRUE and FALSE. It is used particularly when the behaviour of the program is to depend on whether some condition is satisfied - whether it is true or not. integertype: For dealing with positive or negative whole, or integer, numbers. This type cannot be used to deal with non-integer numbers, such as fractions. floatingpointtype: This is a special kind of format which can handle non-integer, rational, numbers (i.e. there is a decimal point) but the position of the decimal point is not fixed. That is, these numbers have a fixed degree of precision - a fixed number of significant digits - but the decimal point can be located anywhere. The decimal point is said to float. Floating points formats are essentially similar to conventional scientific notation such as . stringtype: For storing strings of characters, of variable length. provides functions - or subprograms if you like - to carry out various standard manipulations on objects of these standard types. Specifically, there are functions to display strings in the MS-DOS window, and to read strings in from the keyboard; and to convert between string representations and the internal binary representations of booleantype, integertype and floatingpointtype. In this way, objects of these types can be read in from the keyboard (as stringtype), converted to one of the internal binary representations, operated upon in any appropriate way (i.e. performing boolean or arithmetic operations) and the results can be displayed again. Constants and Data Types booleantype Objects of booleantype may take on only logical or boolean values. The two possible boolean values are denoted by the symbolic constants TRUE and FALSE. integertype Objects of integertype may take on values representing positive or negative integer numbers. Internally these are stored in a 32-bit, 2's complement notation. This means that the range of numbers that can be represented is approximately . The maximum (most positive) and minimum (most negative) values that can be represented are denoted by the symbolic constants INTEGERMAX and INTEGERMIN. Constants of integertype may be represented in your programs in normal, decimal, notation, such as 42 or -4561237 etc. You must not embed commas or spaces in these constants - something like 1,000,000 would not be acceptable. Nor (since these are integer values) should you include a decimal point - not even if the fractional part is given as zero. So, again, something like 45.0 is not regarded as a valid integer value. floatingpointtype Objects of floatingpointtype may take on values representing non-integer, rational, numbers. Internally these are represented in so called floating point format. This is somewhat like normal scientific notation - such as . That is, there is a mantissa ( in this example) and a separate exponent ( in this example). The mantissa has a some finite range which, in effect, determines the number of significant digits that can be represented. The exponent also has a finite range which determines the maximum and minimum absolute values which can be represented. Of course, internally in the computer, both the mantissa and the exponent are represented in binary notation; and the exponent is a power of 2 (normally) rather than a power of 10. In the particular case of floatingpointtype, the mantissa is equivalent to approximately 15 decimal digits; and the exponent is equivalent to approximately . Constants of floatingpointtype may be represented in your programs in normal decimal notation, except that the decimal point must be present, even if the fractional part is intended as zero. Thus, 3.414 is a perfectly good floatingpointtype value, whereas 24 is not - it would be interpreted as an integertype value. If you mean the floatingpointtype value corresponding to the number 24, then code it as 24.0 instead. Very large or small floatingpointtype constants can be represented using scientific notation like this: -23.86e54 (for ) or 0.7433681e-123 (for ). stringtype Objects of stringtype consist of a sequence of arbitrary characters, of arbitrary length, up to a certain maximum. This maximum length is denoted by the symbolic constant STRINGMAXLEN (which is of type integertype). String constants are enclosed by double quote characters, and may not be broken across multiple lines: Within a string constant, certain special characters are denoted by so-called escape sequences as follows: n : This is the newline character. When printed on the screen it moves the cursor down one line and back to the left margin. When stored in a file it marks the break between one line of text and the next. " : This allows you to embed a double quote character in a string (a double quote on its own won't work because it would be mistaken for the end of the string). : Since a backslash character plays a special role in introducing the escape sequences already mentioned, there is a problem if you actually want to have a backslash character itself in your string. The solution is that a double backslash is treated as denoting literally that you want a (single) backslash in the string. Functions putstring() putstring() accepts exactly one argument which must be of stringtype. This may be either a string constant or a string variable. If it is a variable, its value will not be changed by putstring(). putstring() outputs the given string on the standard output stream, or stdout for short. By default, stdout simply corresponds to the MS-DOS window in which the program is being executed. However, when the program is being started, stdout can be redirected into a file on disk. Suppose the executable program is called foo.exe; then the following way of executing the program under DOS would result in stdout being redirected to a file called output.txt (any previous contents of this file being overwritten): putstring does not produce any return value. getstring() getstring() accepts exactly one argument which must be of stringtype, and must be a variable. getstring() reads, or inputs, a string from the standard input stream, or stdin for short, and stores it in the string variable given as an argument. Exactly one line is read - that is, reading of the string is terminated when a newline character is encountered. By default, stdin simply corresponds to the keyboard. Whatever characters are typed in are also echoed in the MS-DOS window in which the program is being executed; pressing the Enter key generates the newline character which terminates reading. Alternatively, when the program is being started, stdin can be redirected to read from a file on disk. Suppose the executable program is called foo.exe; then the following way of executing the program under DOS would result in stdin being redirected to read from a file called input.txt: getstring() produces a return value of booleantype. This will normally be the value TRUE indicating that a string has been successfully read. However, if, for some reason, a string cannot be read, the value FALSE will be produced instead. The usual reason why this might arise would be that stdin has been redirected to come from a disk file, and the end of the file is encountered - naturally, in that case, getstring cannot actually read in a further string. assignstring() assign() accepts exactly two arguments, which must both be of stringtype. The first must be a variable; the second may be a variable or a constant. assignstring() copies or assigns the value of the second argument to the string variable specified by the first argument. The previous value of that variable is overwritten. assignstring() does not produce any return value. stringequal() stringequal() accepts exactly two arguments, which must both be of stringtype. Each can be either a variable or a constant. Neither will be altered by stringequal(). stringequal() produces a return value of booleantype. The two string arguments will be compared, character by character. If both strings are exactly alike (including the case of letters) then stringequal() will produce a return value of TRUE; conversely, if there is any difference between the two strings, stringequal() will produce a return value of FALSE. stringtofloatingpoint() stringtofloatingpoint() accepts exactly one argument, which must be of stringtype. This may be a variable or a constant. However, in practise, it is normally a variable (since, otherwise, a constant of floatingpointtype could simply be used directly, instead of invoking stringtofloatingpoint() to process a string constant). In any case, if the argument is a variable, it will not be altered by stringtofloatingpoint(). stringtofloatingpoint() produces a return value of floatingpointtype. stringtofloatingpoint() examines the string given as an argument, and interprets it (if possible) as a decimal (base 10) number, possibly in scientific notation (e.g. 2.3e5 would denote etc.). Providing that the string can be interpreted successfully in this way, stringtofloatingpoint() will produce the internal binary representation (i.e. of floatingpointtype) of this number as the return value. If the string cannot be interpreted in this way (e.g. rhubarb or forty-two or (01) 704-5000 etc.) then a runtime exception will be generated and the program will terminate abnormally. floatingpointtostring() floatingpointtostring() accepts exactly two arguments. The first must be a variable of stringtype. The second must be of floatingpointtype, and may be either a constant or a variable. However, in practise it will normally be a variable (since, otherwise, a constant of stringtype could simply be used directly, instead of invoking floatingpointtostring()). In any case, if this second argument is a variable, it will not be altered by floatingpointtostring(). floatingpointtostring() takes the value of floatingpointtype given by the second argument, and converts it into a corresponding stringtype value, in decimal (base 10) notation, and stores this string in the variable given as the first argument. floatingpointtostring() does not produce any return value. stringtointeger() stringtointeger() accepts exactly one argument, which must be of stringtype. This may be a variable or a constant. However, in practise, it is normally a variable (since, otherwise, a constant of integertype could simply be used directly, instead of invoking stringtointeger() to process a string constant). In any case, if the argument is a variable, it will not be altered by stringtointeger(). stringtointeger() produces a return value of integertype. stringtointeger() examines the string given as an argument, and interprets it (if possible) as a decimal (base 10) integer number. Providing that the string can be interpreted successfully in this way, stringtointeger() will produce the internal binary representation (i.e. of integertype) of this number as the return value. If the string cannot be interpreted in this way (e.g. rhubarb or forty-two or 3.414 etc.) then a runtime exception will be generated and the program will terminate abnormally. integertostring() integertostring() accepts exactly two arguments. The first must be a variable of stringtype. The second must be of integertype, and may be either a constant or a variable. However, in practise it will normally be a variable (since, otherwise, a constant of stringtype could simply be used directly, instead of invoking integertostring()). In any case, if this second argument is a variable, it will not be altered by integertostring(). integertostring() takes the value of integertype given by the second argument, and converts it into a corresponding stringtype value, in decimal (base 10) notation, and stores this string in the variable given as the first argument. integertostring() does not produce any return value. stringtoboolean() stringtoboolean() accepts exactly one argument, which must be of stringtype. This may be a variable or a constant. However, in practise, it is normally a variable (since, otherwise, a constant of booleantype could simply be used directly, instead of invoking stringtoboolean() to process a string constant). In any case, if the argument is a variable, it will not be altered by stringtodouble. stringtoboolean() produces a return value of booleantype. stringtoboolean examines the string given as an argument, and interprets it (if possible) as a string representation of a boolean value. That is, the string must have the value TRUE or FALSE - except that leading or trailing whitespace will be ignored. Providing that the string can be interpreted successfully in this way, stringtoboolean() will produce the internal binary representation (i.e. of booleantype) of this value as the return value. If the string cannot be interpreted in this way (e.g. rhubarb or True or wrong etc.) then a runtime exception will be generated and the program will terminate abnormally. booleantostring() booleantostring() accepts exactly two arguments. The first must be a variable of stringtype. The second must be of booleantype, and may be either a constant or a variable. However, in practise it will normally be a variable (since, otherwise, a constant of stringtype could simply be used directly, instead of invoking booleantostring()). In any case, if this second argument is a variable, it will not be altered by booleantostring(). booleantostring() takes the value of booleantype given by the second argument, and converts it into a corresponding stringtype value - i.e. either the string TRUE or the string FALSE. It then stores this string in the variable given as the first argument. booleantostring() does not produce any return value. clearscreen() clearscreen() clears the MS-DOS window in which the program is being executed, and places the cursor in the top left position. clearscreen() accepts no arguments, and produces no return value. setcursor() setcursor() accepts exactly two arguments, both of integertype. The first denotes a desired column number, and must be in the range 1 to 80; the second denotes a desired row (or line) number and must be in the range 1 to 25. These can be constants or variables. If they are variables they will not be altered by setcursor(). setcursor() moves the cursor of the MS-DOS window in which the program is running to the specified column and row. Nothing already displayed in the window will be modified. Column 1, row 1 is the top left position of the window. If either the requested column or row is outside the allowed range, setcursor() will generate a runtime exception and the program will be terminated abnormally. setcursor() does not produce any return value. Copyright This Hypermedia Document is copyrighted, 1994, 1995, by Barry McMullinhttp://www.eeng.dcu.ie/7Emcmullin/home.html. Permission is hereby granted to access, copy, or store this work, in whole or in part, for purposes of individual private study only. The work may not be accessed or copied, in whole or in part, for commercial purposes, except with the prior written permission of the author.