main
FunctionA C program starts execution with a function called
main
. The prototype for the main
function
is
int main(int argc, char *argv[]);where
argc
is the number of command-line arguments
and argv
is an array of pointers to the arguments.
When a C program is executed by the kernel — by one of the
exec
functions — a special start-up routine is
called before the main
function is called. The
executable program file specifies this routine as the starting address
for the program; this is set up by the link editor when it is invoked
by the C compiler. This start-up routine takes values from the kernel
— the command line arguments and the environment — and
sets things up so that the main
function is called.
This start-up routine is written so that if main
returns, the exit
function is called. If the start-up
routine were coded in C (it is often coded in assembler) the call to
main
could look like
exec(main(argc,argv));
There are eight ways for a process to terminate. Normal termination occurs in five ways:
main
exit
_exit
or _Exit
pthread_exit
from the last thread
Abnormal termination occurs in three ways:
abort
Three functions terminate a program normally: _exit
and _Exit
, which return to the kernel immediately, and
exit
, which performs certain cleanup processing and then
returns to the kernel.
#include <stdlib.h> void exit(int status); void _Exit(int status); #include <unistd.h> void _exit(int status); |
The reason for the different headers is that exit
and
_Exit
are specified by ISO C whereas _exit
is specified by POSIX.1.
Historically, the exit
function has always performed a clean
shutdown of the standard I/O library: the fclose
function is called
for all open streams. This causes all
buffered output data to be flushed (written to the file).
All three exit functions expect a single integer argument, which we
call the exit status. Most UNIX System shells provide a way to examine
the exit status of a process. If (a) any of these functions is called
without an exit status, (b) main
does a return without a return value,
or (c) the main
function is not declared to return an integer, the
exit status of the process is undefined. However, if the return type
of main is an integer and main "falls off the end" (an implicit
return), the exit status of the process is 0.
Returning an integer value from the main
function is equivalent to
calling exit
with the same value. Thus
exit(0);is the same as
return 0;from the
main
function.
atexit
function#include <stdlib.h> int atexit{void (*func) (void)); |
With ISO C, a process can register up to 32 functions that are
automatically called by exit
. These are called exit handlers and are registered
by calling the atexit
function.
The declaration of atexit
says that we pass the address
of a function as the argument to atexit
. When this function is called, it is
not passed any arguments and is not expected to return a value. The
exit
function calls these functions in reverse order of registration. Each
function is called as many times as it was registered.
exit
first calls the exit handlers and then closes (via
fclose
) all open streams.
Figure 1 summarizes how a C program is started and the various ways it can terminate.
Figure 1 How a C program is started and how it terminates
Note that the only way a program is executed by the kernel is when
one of the exec
functions is called. The only way a
process voluntarily terminates is when _exit
or
_Exit
is called, either explicitly or implicitly (by
calling exit
). A process can also be involuntarily
terminated by a signal (not shown in Figure 1).
The following program demonstrates
different termination routines and the atexit
function.
Historically, a C program has been composed of the following pieces:
int maxcount = 99;appearing outside any function causes this variable to be stored in the initialized data segment with its initial value.
long sum[l000];appearing outside any function causes this variable to be stored in the uninitialized data segment.
Figure 2. Typical memory arrangement
Figure 2 shows the typical arrangement of these segments. This is a logical picture of how a program looks; there is no requirement that a given implementation arrange its memory in this fashion. Nevertheless, this gives us a typical arrangement to describe. With Linux on an Intel x86 processor, the text segment starts at location 0x08048000, and the bottom of the stack starts just below 0xC0000000. (The stack grows from higher-numbered addresses to lower-numbered addresses on this particular architecture.) The unused virtual address space between the top of the heap and the top of the stack is large.
Note from Figure 2 that the contents of the uninitialized data segment are not stored in the program file on disk. This is because the kernel sets it to 0 before the program starts running. The only portions of the program that need to be saved in the program file are the text segment and the initialized data.
The size(l)
command reports the sizes (in bytes) of segments. For example:
$ size /usr/bin/cc /bin/sh text data bss dec hex filename 79606 1536 916 82058 1408a /usr/bin/cc 619234 21120 18260 658614 aOcb6 /bin/shThe fourth and fifth columns are the total of the three sizes, displayed in decimal and hexadecimal, respectively.
Different systems provide different ways for a program to say that
it wants to use or not use the shared libraries. Options for the
cc
(l)
and ld
(l) commands are typical. As an example of the size differences,
the following executable file — the classic hello.c
program
— was first created without shared libraries:
$ gcc -static -o hello hello.c prevent gcc from using shared libraries $ ls -l hello -rwxr-xr-x 1 loomis users 465705 2006-09-14 22:46 hello $ size hello text data bss dec hex filename 399390 3368 4732 407490 637c2 hello
If we compile this program to use shared libraries, the text and data sizes of the executable file are greatly decreased:
$ gcc -o hello hello.c gcc defaults to use shared libaries $ ls -l hello -rwxr-xr-x 1 loomis users 7023 2006-09-14 22:47 hello $ size hello text data bss dec hex filename 1013 260 4 1277 4fd hello
ISO C specifies three functions for memory allocation:
malloc
, which allocates a specified number of
bytes of memory. The initial value of the memory is indeterminate.
calloc
, which allocates space for a specified
number of objects of a specified size. The space is initialized to all
0 bits.
realloc
, which increases or decreases the size of
a previously allocated area.When the size increases, it may involve
moving the previously allocated area somewhere else, to provide the
additional room at the end. Also, when the size increases, the initial
value of the space between the old contents and the end of the new
area is indeterminate.
#include <stdlib.h> void *malloc(size_t size); void *calloc (size_t nobj, size_t size); void *realloc (void *ptr, size_t newsize); void free(void *ptr); |
All three allocation routines return a non-null pointer if OK and NULL on error
The pointer returned by the three allocation functions is
guaranteed to be suitably aligned so that it can be used for any data
object. For example, if the most restrictive alignment requirement on a
particular system requires that doubles
must start at memory locations
that are multiples of 8, then all pointers returned by these three
functions would be so aligned.
Because the three alloc
functions return a generic
void *
pointer,
if we #include <stdlib.h>
(to obtain the function prototypes), we do not
explicitly have to cast the pointer returned by these functions when we
assign it to a pointer of a different type.
The function free
causes the space pointed to by
ptr
to be deallocated. This freed space is usually
put into a pool of available memory and can be allocated in a later
call to one of the three alloc
functions.
The realloc
function lets us increase or decrease the
size of a previously allocated area. (The most common usage is to
increase an area.) For example, if we allocate room for 512 elements
in an array that we fill in at runtime but find that we need room for
more than 512 elements, we can call realloc
. If there is
room beyond the end of the existing region for the requested space,
then realloc
doesn't have to move anything; it simply
allocates the additional area at the end and returns the same pointer
that we passed it. But if there isn't room at the end of the existing
region, realloc
allocates another area that is large
enough, copies the existing 512-element array to the new area, frees the old
area, and returns the pointer to the new area. Because the area may
move we shouldn't have any pointers into this area.
sizes.. .
Note that the final argument to realloc
is the new
size of the region, not the difference between the old and new sizes.
As a special case, if ptr
is a null pointer,
realloc
behaves like malloc
and allocates a region of the
specified newsize
.
The allocation routines are usually implemented with the
sbrk(2)
system call. The system call expands (or contracts) the heap of the
process.
Although sbrk
can expand or contract the memory of a
process, most versions of malloc
and free
never decrease their memory size. The space that we freed is
available for a later allocation, but the freed space is not usually
returned to the kernel; that space is kept in the malloc
pool.
It is important to realize that most implementations allocate a little more space than is requested and use the additional space for record keeping — the size of the allocated block, a pointer to the next allocated block, and the like. This means that writing past the end of an allocated area could overwrite this record-keeping information in a later block. These types of errors are often catastrophic, but difficult to find, because the error may not show up until much later. Also, it is possible to overwrite this record keeping by writing before the start of the allocated area.
Writing past the end or before the beginning of a dynamically-allocated buffer can corrupt more than internal record-keeping information. The memory before and after a dynamically-allocated buffer can potentially be used for other dynamically-allocated objects. These objects can be unrelated to the code corrupting them, making it even more difficult to find the source of the corruption.
Other possible errors that can be fatal are freeing a block that
was already freed and calling free
with a pointer that was not obtained
from one of the three alloc
functions. If a process
calls malloc
,
but forgets to call free
, its memory usage continually increase; this
is called leakage. By not calling free
to return unused
space, the size of a process s address space slowly increases until no free space is
left. During this time, performance can degrade from excess paging
overhead.
Because memory allocation errors are difficult to track down, some
systems provide versions of these functions that do additional error
checking every time one of the three alloc
functions or
free
is called.
These versions of the functions are often specified by including a
special library for the link editor. There are also publicly
available sources that you can compile with special flags to
enable additional runtime checking.
Maintained by John Loomis, last updated 18 September 2006