Memory management:
One
of the most important functions of a programming language is to provide
facilities for managing memory and the objects that are stored in memory. C
provides three distinct ways to allocate memory for objects:
Static
memory allocation: space for the object is provided in the binary at
compile-time; these objects have an extent (or lifetime) as long as the binary
which contains them is loaded into memory.
Automatic
memory allocation: temporary objects can be stored on the stack, and this space
is automatically freed and reusable after the block in which they are declared
is exited.
Dynamic
memory allocation: blocks of memory of arbitrary size can be requested at
run-time using library functions such as malloc from a region of memory called
the heap; these blocks persist until subsequently freed for reuse by calling
the library function realloc or free
These
three approaches are appropriate in different situations and have various
trade-offs. For example, static memory allocation has little allocation
overhead, automatic allocation may involve slightly more overhead, and dynamic
memory allocation can potentially have a great deal of overhead for both
allocation and deallocation. The persistent nature of static objects is useful
for maintaining state information across function calls, automatic allocation
is easy to use but stack space is typically much more limited and transient
than either static memory or heap space, and dynamic memory allocation allows
convenient allocation of objects whose size is known only at run-time. Most C
programs make extensive use of all three.
Where
possible, automatic or static allocation is usually simplest because the
storage is managed by the compiler, freeing the programmer of the potentially
error-prone chore of manually allocating and releasing storage. However, many
data structures can change in size at runtime, and since static allocations
(and automatic allocations before C99) must have a fixed size at compile-time,
there are many situations in which dynamic allocation is necessary.[27] Prior
to the C99 standard, variable-sized arrays were a common example of this. (See
the article on malloc for an example of dynamically allocated arrays.) Unlike
automatic allocation, which can fail at run time with uncontrolled
consequences, the dynamic allocation functions return an indication (in the
form of a null pointer value) when the required storage cannot be allocated.
(Static allocation that is too large is usually detected by the linker or
loader, before the program can even begin execution.)
Unless
otherwise specified, static objects contain zero or null pointer values upon
program startup. Automatically and dynamically allocated objects are
initialized only if an initial value is explicitly specified; otherwise they
initially have indeterminate values (typically, whatever bit pattern happens to
be present in the storage, which might not even represent a valid value for
that type). If the program attempts to access an uninitialized value, the
results are undefined. Many modern compilers try to detect and warn about this
problem, but both false positives and false negatives can occur.
Another
issue is that heap memory allocation has to be synchronized with its actual
usage in any program in order for it to be reused as much as possible. For
example, if the only pointer to a heap memory allocation goes out of scope or
has its value overwritten before free() is called, then that memory cannot be
recovered for later reuse and is essentially lost to the program, a phenomenon
known as a memory leak. Conversely, it is possible for memory to be freed but
continue to be referenced, leading to unpredictable results. Typically, the
symptoms will appear in a portion of the program far removed from the actual
error, making it difficult to track down the problem. (Such issues are
ameliorated in languages with automatic garbage collection.)
No comments:
Post a Comment