Chapter 9
Often a program cannot know in advance how much memory it will require. Setting aside “enough” memory, such as defining a char array of su cient size to hold the largest permitted string, is not convenient in many situations and may be di cult to manage.
Dynamic memory facilitates memory on demand. Memory is requested at runtime as required, and the lifetime of each memory allocation is controlled directly by the programmer.
C has four distinct areas of memory: the constant data area, the static-extent data area, the stack, and the heap. The first of these stores string constants and other data whose values are known at compile time. This area of memory is read-only, and the results of trying to modify it are undefined. The second data area is for variables that are defined extern or static, and so exist for the lifetime of the program. These variables are read-writable. Both of the first two memory areas are allocated when the program begins and destroyed when it terminates, and are managed by the compiler.
The memory area known as the stack is used to store local variables (i.e., variables with automatic extent). A local variable is allocated memory at the point where it is defined and this memory is released immediately as the variable goes out-of-scope. The stack behaves like a last-in-first-out (LIFO) queue. When variables are defined, they are “pushed onto the stack”, and the stack-size increases. And at the end of a block, if several variables go out-of-scope at once, the variables are destroyed, or “popped o the stack”, in reverse order to their allocation. Stack memory allocation is managed entirely by the compiler.
The heap memory area is for dynamically allocated storage and is managed, not by the com-piler, but explicitly by the programmer. Requests for memory, and its subsequent destruction, are performed via a set of standard library functions, and the programmer has complete control over the lifetime of an allocated memory block. The flexibility and control provided by heap-allocated memory comes at the price of added responsibility on behalf of the programmer. The compiler does not check that the memory is managed correctly, and dynamic memory errors are a plentiful source of subtle runtime bugs.
It is worth noting that stack memory allocation is generally much faster than heap memory allocation, as stack allocation involves only an increment of the stack pointer, while heap allocation involves much more complex operations. For this reason, stack allocation is often preferred over heap allocation even at the cost of some wasted storage space. For example, an over-size char array, allocated on the stack, is often used to perform string operations rather than an exact-size array created on demand on the heap.
The two key standard library functions for dynamic memory allocation and de-allocation, respec-tively, are malloc() and free(). The first, malloc() has the following interface,
void *malloc(size_t size)
where size is the number of bytes of memory to allocate, and the return value is a pointer to this requested memory. Notice that the returned pointer is of type void*, which specifies a generic pointer, and can represent a pointer of any type. For example, to create an array of 10 integers, one writes
int *p = malloc(10 * sizeof(int)); /* allocate bytes for 10 integers */
and the return value of malloc() is automatically converted to type int *. It is common to write an explicit cast to the desired type so as to clarify intent.1
int *p = (int *) malloc(10 * sizeof(int));
Typically requests for memory using malloc() will succeed, and the returned pointer points to a valid memory location. However, it is possible for the request to fail (e.g., if the available heap-space is full; a rare event on modern machines, but still possible), and when this happens malloc() returns a NULL pointer. It is important to always check the return value of malloc() for NULL so as to prevent invalid attempts to dereference the NULL pointer.
The de-allocation function free() releases memory allocated by malloc(), and returns it to the heap. It has the following interface,
void free(void *)
and is used as follows (continuing from the previous example).
free(p);
The conversion of a pointer of type int* to a pointer of type void* does not require an explicit cast (in C or C++), and so casts of this variety never appear in practice.
There are two other dynamic allocation functions in the C standard library, calloc() and realloc(). The first, calloc(), behaves almost the same as malloc() but, where malloc() returns a block of memory that is uninitialised (i.e., the cells contain arbitrary values), calloc() initialises the block with zeros. The interface for calloc() is slightly di erent to malloc() as it take two arguments
void *calloc(size_t n, size_t size)
where the first specifies the number of objects in the requested array, and the second specifies the size of the object type. For example, the following statement allocates an array of 10 integers initialised to zero.
int *p = calloc(10, sizeof(int)); /* allocate array of 10 integers, all 0 */
As for malloc(), calloc() returns NULL if the memory request fails.
The final memory allocation function, realloc(), is used to change the size of an existing block of dynamically allocated memory. That is, given a block of memory allocated by malloc(), calloc(), or realloc() itself, realloc() will adjust the size of the allocated memory to the new requested size. The interface of realloc() is as follows
1 Another reason for an explicit cast is for compatibility with C++, which requires a cast for conversions from void* to a pointer of another type. Thus, in the above text, the second example is valid C++ code but the first is not.
void *realloc(void *p, size_t size)
where p is a pointer to the current block of memory and size is the new requested size. The return value is a pointer to the resized memory block, or NULL if the request fails. Also, if realloc() is passed a size request of 0, then the memory pointed to by p is released, and realloc() returns NULL. In this case NULL does not indicate failure.
Calling realloc() does not change the existing contents of a memory block. If the block is reduced, then the excess cells are truncated and the values of the remaining cells are left unchanged. If, on the other hand, the block is increased, the old values are retained and the new cells will contain uninitialised (i.e., arbitrary) values. The action of realloc() is to grow or shrink a region of memory. If this can be done in-place, then realloc() might simply adjust certain bounds records and return but, if there is insu cient space in the current region within the heap, then realloc() will allocate a new block of memory of the appropriate size, copy across the values of the previous block, and release the old block. These operations are managed internally by realloc(), but they may a ect application code if it has pointers into the old memory block; these pointers will be invalid if the block is copied elsewhere.
Dynamic Memory
Often a program cannot know in advance how much memory it will require. Setting aside “enough” memory, such as defining a char array of su cient size to hold the largest permitted string, is not convenient in many situations and may be di cult to manage.
Dynamic memory facilitates memory on demand. Memory is requested at runtime as required, and the lifetime of each memory allocation is controlled directly by the programmer.
9.1 Di erent Memory Areas in C
C has four distinct areas of memory: the constant data area, the static-extent data area, the stack, and the heap. The first of these stores string constants and other data whose values are known at compile time. This area of memory is read-only, and the results of trying to modify it are undefined. The second data area is for variables that are defined extern or static, and so exist for the lifetime of the program. These variables are read-writable. Both of the first two memory areas are allocated when the program begins and destroyed when it terminates, and are managed by the compiler.
The memory area known as the stack is used to store local variables (i.e., variables with automatic extent). A local variable is allocated memory at the point where it is defined and this memory is released immediately as the variable goes out-of-scope. The stack behaves like a last-in-first-out (LIFO) queue. When variables are defined, they are “pushed onto the stack”, and the stack-size increases. And at the end of a block, if several variables go out-of-scope at once, the variables are destroyed, or “popped o the stack”, in reverse order to their allocation. Stack memory allocation is managed entirely by the compiler.
The heap memory area is for dynamically allocated storage and is managed, not by the com-piler, but explicitly by the programmer. Requests for memory, and its subsequent destruction, are performed via a set of standard library functions, and the programmer has complete control over the lifetime of an allocated memory block. The flexibility and control provided by heap-allocated memory comes at the price of added responsibility on behalf of the programmer. The compiler does not check that the memory is managed correctly, and dynamic memory errors are a plentiful source of subtle runtime bugs.
It is worth noting that stack memory allocation is generally much faster than heap memory allocation, as stack allocation involves only an increment of the stack pointer, while heap allocation involves much more complex operations. For this reason, stack allocation is often preferred over heap allocation even at the cost of some wasted storage space. For example, an over-size char array, allocated on the stack, is often used to perform string operations rather than an exact-size array created on demand on the heap.
9.2 Standard Memory Allocation Functions
The two key standard library functions for dynamic memory allocation and de-allocation, respec-tively, are malloc() and free(). The first, malloc() has the following interface,
void *malloc(size_t size)
where size is the number of bytes of memory to allocate, and the return value is a pointer to this requested memory. Notice that the returned pointer is of type void*, which specifies a generic pointer, and can represent a pointer of any type. For example, to create an array of 10 integers, one writes
int *p = malloc(10 * sizeof(int)); /* allocate bytes for 10 integers */
and the return value of malloc() is automatically converted to type int *. It is common to write an explicit cast to the desired type so as to clarify intent.1
int *p = (int *) malloc(10 * sizeof(int));
Typically requests for memory using malloc() will succeed, and the returned pointer points to a valid memory location. However, it is possible for the request to fail (e.g., if the available heap-space is full; a rare event on modern machines, but still possible), and when this happens malloc() returns a NULL pointer. It is important to always check the return value of malloc() for NULL so as to prevent invalid attempts to dereference the NULL pointer.
The de-allocation function free() releases memory allocated by malloc(), and returns it to the heap. It has the following interface,
void free(void *)
and is used as follows (continuing from the previous example).
free(p);
The conversion of a pointer of type int* to a pointer of type void* does not require an explicit cast (in C or C++), and so casts of this variety never appear in practice.
There are two other dynamic allocation functions in the C standard library, calloc() and realloc(). The first, calloc(), behaves almost the same as malloc() but, where malloc() returns a block of memory that is uninitialised (i.e., the cells contain arbitrary values), calloc() initialises the block with zeros. The interface for calloc() is slightly di erent to malloc() as it take two arguments
void *calloc(size_t n, size_t size)
where the first specifies the number of objects in the requested array, and the second specifies the size of the object type. For example, the following statement allocates an array of 10 integers initialised to zero.
int *p = calloc(10, sizeof(int)); /* allocate array of 10 integers, all 0 */
As for malloc(), calloc() returns NULL if the memory request fails.
The final memory allocation function, realloc(), is used to change the size of an existing block of dynamically allocated memory. That is, given a block of memory allocated by malloc(), calloc(), or realloc() itself, realloc() will adjust the size of the allocated memory to the new requested size. The interface of realloc() is as follows
1 Another reason for an explicit cast is for compatibility with C++, which requires a cast for conversions from void* to a pointer of another type. Thus, in the above text, the second example is valid C++ code but the first is not.
void *realloc(void *p, size_t size)
where p is a pointer to the current block of memory and size is the new requested size. The return value is a pointer to the resized memory block, or NULL if the request fails. Also, if realloc() is passed a size request of 0, then the memory pointed to by p is released, and realloc() returns NULL. In this case NULL does not indicate failure.
Calling realloc() does not change the existing contents of a memory block. If the block is reduced, then the excess cells are truncated and the values of the remaining cells are left unchanged. If, on the other hand, the block is increased, the old values are retained and the new cells will contain uninitialised (i.e., arbitrary) values. The action of realloc() is to grow or shrink a region of memory. If this can be done in-place, then realloc() might simply adjust certain bounds records and return but, if there is insu cient space in the current region within the heap, then realloc() will allocate a new block of memory of the appropriate size, copy across the values of the previous block, and release the old block. These operations are managed internally by realloc(), but they may a ect application code if it has pointers into the old memory block; these pointers will be invalid if the block is copied elsewhere.
No comments:
Post a Comment