Memory Management
Proper memory management is critical in application development. Careful attention has been paid in previous code samples to ensure that the compiler always implicitly provided the memory space necessary.
Memory management can be implicit or explicit. C provides built-in functions enabling a programmer to allocate and free memory explicitly. It is also possible that the necessary memory is implied by the manner in which a variable is declared.
Samples of associating memory implicitly with a variable include
char str1[10];
where the variable array str1, upon declaration, has sufficient space for storing 10 characters.
Consider also
char *buf = "No error";
where the variable buf is declared with space enough to hold 8 characters as implied by the initialization combined with the declaration.
Memory associated with variables can be taken from one of several areas available to an application. Variables declared after a start-of-body marker reside on the stack. (See Chapter 1 for a review of automatic variables and the stack.) The stack is volatile memory changing constantly during program execution.
Note
A programmer affects the stack through the manner in which variables are declared. In other words, the programmer can never explicitly allocate or free stack memory.
The memory associated with the variable buf in the previous code fragment resides on the stack until it is out of scope. After it is no longer visible, the stack frame holding the reference to buf is removed from the stack and a new frame uses the memory.
Contrast the declaration of the two character pointers in the following example:
char *buf = "No error", *token;
The variables buf and token both point to characters with the contents of buf being initialized at the moment of its declaration. Based on buf's initialization, you know both its size (length) and contents. Because no memory is associated with token, it has neither a length nor valid contents. Token is a character pointer, but as of yet points to nothing (garbage).
The cumbersome task of memory management thus begins.
A valid statement is to assign the memory associated with buf to token.
token = buf;
Because the variables are compatible (both character pointers) the assignment is legal and logical; now, token refers to valid memory (the implicit memory given to buf from the stack). Employing token follows the same rule as using buf, namely, the size of the memory provided cannot be exceeded (eight characters).
Furthermore, when the variables go out of scope, their contents are no longer valid because a new stack frame will begin using the memory that was once reserved for them.
Consider this incorrect code sample in which the memory associated with an automatic variable is returned to the calling function:
1: // forward declaration 2: char *someFunc( void ); 3: 4: // entry point of the program 5: int main( int argc, char *argv[] ) 6: { 7: char *badString = someFunc(); 8: printf("The value of str is: " ); 8a: sleep( 1 ); // new stack frame 9: printf( "'%s'\n", badString ); 10: return( 0 ); 11: } 12: 13: char *someFunc( void ) 14: { 15: char buf[15]; 16: strcpy( buf, "Error ahead" ); 17: // return the memory provided from the 18: // the stack for the variable buf 19: return( buf ); 20: }
As described in the previous example, when the function main calls someFunc, a new frame is placed on the stack to manage all aspects of the function call. Specifics such as the function arguments, the return address of the calling function, the return value, and the local (automatic) variables, as well as temporary storage needed during function evaluation, are part of the stack frame.
Therefore, the memory used to store the value of buf in the function someFunc resides in the stack frame managing the function call. When the function returns, the frame is removed and all associated memory is made available for subsequent function calls.
Note
A distinction must be made between returning pointers (addresses) from a function and returning values.
The caution being issued in the previous example is for returning addresses to data values whose location is on the stack and therefore in volatile memory.
The variable buf is a pointer to a value and not the value itself. Returning the value presents no danger because a copy of the data is made before returning it to the calling function.
Returning a value is not always practical, however, because the size of a data structure can adversely affect program performance.
Warning
Returning and employing memory from the stack creates memory errors that will likely result in a program crash when the stack frame is no longer active.
In the previous example, if main did not exit immediately but instead invoked another function, a new frame would be placed on the stack and the memory previously associated with buf (still pointed to by badString) would be reused. The new function owning the memory location would store a new value, possibly a value that is not character data. This would ensure that the value of badString is not as expected and a program crash could soon occur.
To avoid the problem of returning memory contained on the stack, you must explicitly allocate memory associated with pointer variables or reserve them in non-stack memory if their use extends beyond a single function.
The static Keyword
The keyword static serves several purposes in the C programming language. One of its uses is to force the memory associated with a variable to come from non-stack memory. This is beneficial because the memory will be persistent through the life of the program and not just the life of the function, as we saw with the previous example.
Use of the static keyword follows the form
static data type variable name;
Applying this to the previous example to correct the imminent program crash would require that the declaration of the variable buf change from
15: char buf[15];
to
15: static char buf[15];
As stated previously, the static keyword informs the compiler that the memory associated with the variable must not be taken from the stack. Instead, persistent memory is used and therefore the contents of buf are maintained during the life of the program. In addition to the memory address being safely returned to the calling function with the line
19: return( buf );
the contents of the variable will be maintained in consecutive calls to the function. More examples of static automatic variables will be seen in the Graphics Editor project.
Note
Memory associated with static variables can never be returned or unreserved by a program. Therefore, memory reserved as static permanently increases the size of a program.
Note
You can also use the static keyword beyond making a variable's memory persistent to limit the variable's scope.
» As described earlier in this chapter, in the section "Variable Scope" on page 88, variables declared outside of a function body are global variables.
Global variables are visible (in scope) from the moment of their declaration until the end of the file. Global variables can be made visible to other files through use of the keyword extern.
The extern Keyword
The keyword extern can preface any variable declaration as a method of informing the compiler that the variable's actual declaration is external to the current file.
The declaration
extern int sum; // references a previous declaration of sum
tells the compiler that use of the variable sum is by merit of a previous declaration in another file. By using extern, the variable is global (visible) to multiple files.
However, if the original (actual) declaration employed the static keyword, visibility would be limited to the file in which it was declared. In other words, when you use the keyword static on a global variable, the variable cannot be declared externally through use of the extern keyword in another file.
Similarly, when functions are prefaced with the static keyword, their visibility, too, is limited to the file in which they are declared even if a prototype for the function exists elsewhere.
A function defined as
static int add2Nums( int num1, int num2 ) { return( num1 + num2); }
can only be invoked by functions in the same file, which contains this definition because the static keyword limits its visibility.
Using the static keyword in the context of ensuring that a variable's memory is persistent during program execution is one method of managing memory that doesn't employ the stack. Another method of managing memory is to dynamically request it from an area known as the heap.