Built-In Functions
You will not have to author all the functions that your application employs because the C programming language provides many functions for you. When you become aware of functions inherent to a language, you increase your proficiency in it.
The availability of functions provided by an environment such as X Window or a third-party package adds to this challenge.
Third-party packages are ones that you add to your system and are generally not part of the standard installation process. These packages are usually purchased separately and selected for the functionality they add to your development or runtime environment. The OSF/Motif Widget Set is an example of this; it compliments the X Window System because it provides elegant three-dimensional features to X Window-based application development.
Focusing on some of the functions provided by the C programming language, look again at the code sample from the Hello World example that started the chapter:
3: printf("Hello World");
The printf Command
The printf command is a function the C language provides for printing formatted output to the terminal. The use of printf is extremely flexible because it supports every data type recognized by the C language.
EXCURSION
Terminals Aren't Just Where Planes Depart
A terminal may be your screen or a terminal emulator (window) such as the xterm depending on whether your system is running in graphics or text mode.
Generally, printf places its output on the command line following where the program was executed. This destination is known as standard out and represented in C as stdout. In addition to standard out, the C language also provides the destination standard error (stderr) for output.
Standard out and standard error start as the same destination; however, they can be altered through the use of redirection as discussed in the section "grep, Pipes, Redirection, and more" in Chapter 1, page 44.
Chapter 4 covers windowing concepts and terminal emulators in detail.
As with the function add2Nums defined earlier, there must be a forward declaration (prototype) for the function printf. Because the C language provides the function, it also provides the prototype.
The printf function's declaration (and many other functions performing input and output) is found in the file stdio.h, which is part of the standard C header files.
EXCURSION
Another Look at the Use of Header Files
Header files, as discussed in the Note found in the section "Definition" in Chapter 2, page 68, are source files often containing prototypes and other declarations shared by multiple files within a project.
You include a header file by using the include compiler directive. As the name implies, a compiler directive directs the compiler to perform a task or make a decision.
In C, all compiler directives are prefaced with the pound sign (#).
Used in the same manner as the include statement from Makefile syntax as discussed in Chapter 1, section "Include" on page 37, the compiler directive to include a header file performs the inclusion at the point of the directive. The effect is that the compiler sees the contents of the file as if replicated by the directive.#include <stdio.h>
The syntax when employing the include compiler directive offers a hint to where the file can reside. Specifically, when the header is part of the standard C library the < and > are used to enclose the filename. However, when the header file is part of a third-party package or one that you have authored as part of the project, the filename is enclosed by double quotes.
The C compiler first looks in a standard directory such as /usr/include for files enclosed with the < > symbols and then it considers the paths specified by the -I flag passed when the compiler was invoked.#include "gxgraphics.h".
To review the gcc command and use of the -I flag, refer to Chapter 1, section "gcc -I", page 28.
To properly declare the printf function prior to its invocation, add the following line to the "Hello World" code sample:
0a: #include <stdio.h>
The syntax of the printf function follows the form
printf( "format string" [, arg][, arg][, ...] );
To accomplish the formatting recognized by the printf function, the format string ranges from a constant such as "Hello World" to accepting a variety of formatting tokens.
Note
Notice in the syntax of the printf that the args are enclosed in square braces ([]), indicating that they are optional. An argument (arg) is only required if a format token is nested in the format string.
Table 3.1 shows some common formatting tokens recognized by printf that can be embedded in the format string.
Table 3.1 Printf Formatting Tokens
Token |
Type |
Sample |
Output |
%s |
char * |
printf("String: %s", "Hello" ); |
String: Hello |
%c |
char, char |
printf( "Character: %c", 'A' ); |
Character: A |
%d |
int, long, short, unsigned int, unsigned long, unsigned short |
printf( "Number: %d", 198 ); |
Number: 198 |
%f |
float, double |
printf( "Real Number: %f", 3.14 |
Real Number: 3.14 ); |
All formatting tokens are prefaced with the percent sign (%), indicating to printf that what follows is for argument substitution. Further, for each token in the format string there must be a corresponding argument to satisfy the substitution.
Multiple arguments are comma separated and substituted in the order they are placed.
printf( "String %s and Char: %c", "Hello", 'A' );
Note
As demonstrated in the previous example, C uses double quotes ("") to reresent multiple characters (strings) and single quotes to represent a single character ('').
Note
printf's capability to perform type checking is limited. A token nested in the format string expects a complimenting argument of a specific type. If an argument of a differing type is placed in the argument list, the results cannot be predicted. The printf statement will attempt to cast the argument, but because this is done at run-time (while the program executes) there is no recovery if the types are not compatible.
EXCURSION
Promoting Variable Data Types to Satisfy Type Checking
Casting from one data type to another is a way to promote variables to satisfy type requirements and avoid compiler warnings. For instance, a character (char) is easily promoted to an integer (int), which has a larger storage capacity.
The code fragment
char chr = 'A';
int num = (int)chr;
assigns the letter A to the variable chr. Because the letter A has a decimal equivalence of 65, casting chr to an int would assign the value 65 to the variable num.
Caution must be used when casting from a larger data size to a smaller, however, because data could be lost. Consider the fragment
int bigNum = 999; char chr = (char)bigNum;
Because the maximum value of a character is 256, the assignment of bigNum to chr, although made legal by the cast, results in the new value of chr being 25. Clearly, this is not the expected result.
See the section "Data Types" in Chapter 2, page 70 for a review of valid value ranges and the implicit size of recognized data types.
Looking again at the format string accepted by the printf function, it should be clear why printf is said to perform formatted output. Additional formatting tokens and token modifiers are available to printf for outputting data types in varying forms as well as controlling field widths, alignment, and more.
Review the printf man page for a full description of its capabilities.
The printf is one of several functions that C provides for performing formatted output. Similar to printf are the functions fprintf and sprintf.
Both fprintf and sprintf enable the output to be directed some place other than standard out. For instance, fprintf may be used to send the formatted output to standard error.
fprintf( stderr, "%s %s", "hello", "world" );
Differing from the syntax of printf, fprintf requires as its first parameter the destination designator for the output. This destination can be one that C provides, such as stderr used in the example, or it can be one created by using the fopen (file open) function.
Before looking at the use of fopen, consider sprintf as it relates to the printf and fprintf functions. Use of sprintf enables a programmer to format output for placement in a buffer.
A buffer is a character array used for intermediate storage during input or output operations. For instance, in
char message[25];
message is declared as an array of 25 characters and could be used to satisfy the first parameter required by sprintf.
sprintf( message, "Error occurred at line %d", lineno );
EXCURSION
Apply Great Caution when Determining the Correct Size of an Array
In the sprintf example, message was declared with a length of 25. Count the characters in the format string passed to the function:
"Error occurred at line %d"
Including spaces, there are 23 characters in the format string, excluding the value of the formatting token %d.
The C function sprintf null terminates the output that it formats: It inserts a null character (\0) at the end of the string. This termination is important for other C functions that can act on the string and it consumes one place in the buffer.
With the 23 characters in the format string and one character for null termination, a total of 24 characters are placed in the buffer message before the argument substitution for the token %d. If the value of lineno substituted in the message is only one digit (09), you have exactly filled the 25 character spaces available to message. However, if the value of lineno is greater than 9 (two or more digits), you will exceed the length of message because the null termination will be placed outside the valid memory associated with the buffer.
Figure 3.2 illustrates the effect on memory when the boundary of an array is exceeded, a condition known as a segmentation violation.
The value 69 placed in the buffer exceeds the allowed space for message. The owner of the memory that the \0 (null) overwrites is unknown. The memory can be unused or it can be a critical part of the program. Depending on the importance of the unknown space, the program might crash instantly, or it might only corrupt the value of the neighboring space, leading to a crash much later in program execution.
Note
Improper use of arrays is one of the most common causes of program bugs.
A program bug is anything that causes a program to behave unexpectedly, often resulting in a crash.
Now that we've reviewed the built-in functions of printf, fprintf, and sprintf for formatting and outputting data, we can return to the use of the fopen function for creating destination designators to be passed to fprintf.
The fopen Function
The fopen function opens a file named in the parameter list returning a handle to the file known as a file pointer.
Incorporating everything discussed thus far, consider the following code sample illustrating the use of the fopen function:
1: #include <stdio.h> // for printf and fopen function prototypes 1a: // and FILE structure definition 2: FILE *openFile( char * filename ) 3: { 4: FILE *fp; 5: 6: fp = fopen( filename, "w+" ); 7: 8: if( fp == NULL ) { 9: fprintf( stderr, "Unable to open file %s", filename); 10: 11: } 12: return( fp ); 13: }
The code sample defines a function named openFile that accepts a single parameter filename and a character pointer, and returns a pointer to FILE.
Note
The FILE data type is a structure that C provides for referencing files opened with the fopen function. The structure is considered opaque, meaning that the fields defined within the structure are not to be accessed. The structure exists only to serve as a handle for manipulating files.
The standard out (stdout) and standard error (stderr) references provided by C are pointers to the FILE structure (FILE *).
In the code sample demonstrating use of the fopen function, notice the test on the value returned by the fopen.
8: if( fp == NULL ) {
If fopen is unable to open the file specified, it returns NULL.
C provides NULL as a means of representing nothing. Literally defined as 0 (zero) and cast to a void pointer ((void *)0), it is returned in cases of failure to create a valid reference to a return value by functions such as fopen.
The fopen command expects two parameters. The first, evident by the example, is the name of the file to open:
6: fp = fopen( filename, "w+" );
The second parameter is the mode in which fopen should open the file.
Table 3.2 shows the valid modes that can be passed to the fopen function and describes their effect.
Table 3.2 File Modes Understood by fopen
Mode |
Description |
r |
Open the file for reading |
r+ |
Open the file for reading or writing |
w, w+ |
Truncate to zero length or create; file is opened for writing |
a, a+ |
Append, open, or create file for update, writing at the end of the file |
Upon successfully opening a file, fopen returns a file pointer (FILE *) reference which can be passed to functions requiring a destination designator such as fprintf:
fprintf( fp, "Line entered into file referenced by fp" );
Now that you're comfortable with the code sample illustrating the fprintf command, the use of NULL, function bodies, rules of scope, and variable and function declarations, we will consider another family of functions provided by C.
The C String Library
The lexical analysis of strings as discussed in Chapter 2, the section "Types of Conditions," page 52, was said to require a different method from integers and characters for forming test conditions. Because string manipulation is language specific, the C string library satisfies this requirement.
C provides many built-in functions for comparing, copying, and creating strings. The first to consider is the function for comparing two strings called strcmp (string compare). Passing the two strings for comparison satisfies the parameters required by the strcmp function:
1: #include <strings.h> 2: 3: char *str1 = "tag", 4: *str2 = "day"; 5: 6: if( strcmp( str1, str2 ) == 0 ) { 7: // the strings match 8: } else if( strcmp( str1, str2 ) < 0 ) { 9: // str1 appears before str2 in a dictionary 10: } else if( strcmp( str1, str2 ) > 0 ) { 11: // str1 comes after str2 in a dictionary 12: } 13: }
Notice that this code sample uses the include compiler directive to include the file strings.h:
1: #include <strings.h>
This is the header file that C provides to satisfy the built-in string functions' forward declarations.
EXCURSION
Combining Declarations and Assignments Using C Syntax
A variation on variable declaration appears in this code sample as the declaration of str1 and str2 are combined sharing the data type char.
3: char *str1 = "tag", 4: *str2 = "day";
Syntactically correct, multiple variables of the same type can be comma separated at their declaration.
Seen previously but not explicitly noted is the combination of a variable's declaration and its initialization as shown in the previous example.
A sound programming habit is to initialize a variable prior to the variable's use. It saves you from having to scour code later looking for the obscure bug that lack of initialization might cause if the variable is employed in an expression before being assigned an initial value.
As implied by using strcmp in the previous code sample, the comparison of the two strings returns 0 if the strings are equal. The strcmp function returns a value greater than (or less than) 0, depending on the lexical analysis of the two strings.
EXCURSION
The Lexical Analysis of Two Strings of Varying Length
If the two strings being compared by strcmp are not the same length, as in
then the function compares the strings up to the point of finding the NULL termination or end-of-string marker. (See the Excursion in the section about sprintf for a description of string termination performed by C).char *s1 = "act", *s2 = "abra"; if( strcmp( s1, s2 ) == 0 ) {
It is possible to inform C how many characters of the two strings to compare by use of the strncmp function.
The strncmp function accepts one more parameter than its sister function strcmp. Specifically, the third parameter informs strncmp of the number of characters to consider in the comparison.
char *s1 = "act", *s2 = "abra"; if( strncmp( s1, s2, 3 ) == 0 ) {
In this example, you've explicitly said to stop comparing after three characters.
How To Pronounce It
The function strncmp is read like it is written stir-n-compare.
Table 3.3 shows several string functions that C provides; they are common in programs employing the language.
Table 3.3 C String Functions
Function |
Example |
Description |
strcmp, strncmp |
if(strcmp(str1, str2) == 0) { or if(strncmp(str1, str2, 4) == 0){ |
Compare two strings (optionally specifying number of characters to compare) |
strcpy, strncpy |
char str1[10]; strcpy( str1 "Initialize"); strncpy(str1, "Initialize", 10); |
Copy one string into another, (optionally specifying number copy); Caution: the destination string, str1, must have space available for the number of characters being copied to avoid a segmentation violation |
strcat, strncat |
char str1[10]; strcat( str1, "Warn" ); strcat( str1, "ing " ); strncat( str1, "at line", 2 ); |
Concatenate two strings; the second string specified is added to the end of str1 (optionally specify number of characters to add to the end of str1); Caution: the destination string, str1, must have space available to hold the contents of the second string or a segmentation violation will occur |
strchr |
char *token, *buf = token = strchr |
Find the first occurrence of a character within a string; function returns either a pointer to the position of the character within the first parameter, or NULL if no match was found |
strstr |
char *token, *buf = "No error"; token = strstr( buf, "err"); |
Find the first occurrence of a sub-string within a string; function returns a pointer to the start of the sub-string within the first parameter or NULL if no match was found |
strdup |
char *newStr = strdup( "error_log"); |
Duplicate a string, returns a new copy of the string specified to the function |
strlen |
int len = strlen ( newStr ); |
Return the length (number of characters) comprising the string |
Table 3.3 shows that dangers are associated with string manipulation because the space available when copying strings must be sufficient to hold the new contents. The idea of space in a computer program always translates to memory.
The caution extended in the introduction to pointer manipulation, variables by reference, (see the section "Data Types" in Chapter 2, page 70) must be applied to strings as well. In fact, it is the same warning described in the Excursion in the section "sprintf" earlier in this chapter. The common thread is a necessity for proper memory management.