2.9 Structure of Static ObjectsStatic variables are commonly used in the C implementation of a data structure as an object. The data structure and all the functions that access it are placed in a single source file, and the data structure is defined outside any function. The data structure has the static attribute, giving it internal linkage: it is private to that source file. Any references to the data structure outside the file are made through the access functions (methods, in object-oriented terminology) defined within the file. The actual details of the data structure should be invisible to the outside world so that a change in the internal implementation does not require a change to the calling program. You can often make an object thread-safe by placing locking mechanisms in its access functions without affecting outside callers.This section develops an implementation of a list object organized according to the type of static structure just described. Each element of the list consists of a time and a string of arbitrary length. The user can store items in the list object and traverse the list object to examine the contents of the list. The user may not modify data that has already been put in the list. This list object is useful for logging operations such as keeping a list of commands executed by a program.The requirements make the implementation of the list both challenging and interesting. Since the user cannot modify data items once they are inserted, the implementation must make sure that no caller has access to a pointer to an item stored in the list. To satisfy this requirement, the implementation adds to the list a pointer to a copy of the string rather than a pointer to the original string. Also, when the user retrieves data from the list, the implementation returns a pointer to a copy of the data rather than a pointer to the actual data. In the latter case, the caller is responsible for freeing the memory occupied by the copy.The trickiest part of the implementation is the traversal of the list. During a traversal, the list must save the current position to know where to start the next request. We do not want to do this the way strtok does, since this approach would make the list object unsafe for multiple simultaneous traversals. We also do not want to use the strtok_r strategy, which requires the calling program to provide a location for storing a pointer to the next entry in the list. This pointer would allow the calling program to modify entries in the list, a feature we have ruled out in the specification.We solve this problem by providing the caller with a key value to use in traversing the list. The list object keeps an array of pointers to items in the list indexed by the key. The memory used by these pointers should be freed or reused when the key is no longer needed so that the implementation does not consume unnecessary memory resources.Program 2.6 shows the listlib.h file containing the prototypes of the four access functions: accessdata, adddata, getdata and freekey. The data_t structure holds a time_t value (time) and a pointer to a character string of undetermined length (string). Programs that use the list must include the listlib.h header file. Program 2.6 listlib.hThe header file listlib.h.
Program 2.7 shows an implementation of the list object. The adddata function inserts a copy of the data item at the end of the list. The getdata function copies the next item in the traversal of the list into a user-supplied buffer of type data_t. The getdata function allocates memory for the copy of the string field of this data buffer, and the caller is responsible for freeing it.The accessdata function returns an integer key for traversing the data list. Each key value produces an independent traversal starting from the beginning of the list. When the key is no longer needed, the caller can free the key resources by calling freekey. The key is also freed when the getdata function gives a NULL pointer for the string field of *datap to signify that there are no more entries to examine. Do not call freekey once you have reached the end of the list.If successful, accessdata returns a valid nonnegative key. The other three functions return 0 if successful. If unsuccessful, these functions return 1 and set errno. Program 2.7 listlib.cA list object implementation.
The implementation of Sections 13.2.3 and 13.6 discuss how this can be done. Exercise 2.21What happens if you try to access an empty list in Program 2.7?Answer:The accessdata returns 1, indicating an error.Program 2.8 executes commands and keeps an internal history, using the list data object of Program 2.7. The program takes an optional command-line argument, history. If history is present, the program outputs a history of commands run thus far whenever the program reads the string "history" from standard input.Program 2.8 calls runproc to run the command and showhistory to display the history of commands that were run. The program uses fgets instead of gets to prevent a buffer overrun on input. MAX_CANON is a constant specifying the maximum number of bytes in a terminal input line. If MAX_CANON is not defined in limits.h, then the maximum line length depends on the particular device and the program sets the value to 8192 bytes.Program 2.9 shows the source file containing the runproc and showhistory functions. When runproc successfully executes a command, it adds a node to the history list by calling adddata. The showhistory function displays the contents of each node in the list by calling the getdata function. After displaying the string in a data item, showhistory function frees the memory allocated by the getdata call. The showhistory function does not call freekey explicitly because it does a complete traversal of the list. Program 2.8 keeplog.cA main program that reads commands from standard input and executes them.
The runproc function of Program 2.9 calls the system function to execute a command. The runproc function returns 0 if the command can be executed. If the command cannot be executed, runproc returns 1 with errno set.The system function passes the command parameter to a command processor for execution. It behaves as if a child process were created with fork and the child process invoked sh with execl.
If command is NULL, the system function always returns a nonzero value to mean that a command language interpreter is available. If command is not NULL, system returns the termination status of the command language interpreter after the execution of command. If system could not fork a child or get the termination status, it returns 1 and sets errno. A zero termination status generally indicates successful completion. Program 2.9 keeploglib.cThe file keeploglib.c.
|