Here is the transcript of an interactive session with the first demo:
$ ./xmalloc-demo-1 allocating 1000 bytes memory freed
allocating 0 bytes
xmalloc-demo-1.c:line 18: malloc(0) failed
As we see, the program allocates 1000 bytes successfully and then frees it. Next, it attempts to allocate 0 bytes. Our program refuses such a request (what does itmeanto allocate 0 bytes?), so it prints a diagnostic message and exits. (See Part 7.1 of Section 7.5 for more on this.)
Here is the transcript of an interactive session with the second demo:
./xmalloc-demo-2
1: 1000000000 bytes allocated 2: 1000000000 bytes allocated (the next 140730 lines deleted) 140734: 1000000000 bytes allocated 140735: 1000000000 bytes allocated
xmalloc-demo-2.c:line 10: malloc(1000000000) failed
This demo allocates memory chunks of 1,000,000,000 bytes (1 gigabyte) in an unending while-loop without freeing them. It prints a line upon each pass through the loop. After stepping through the loop 140,735 times, my computer’s memory is exhausted; therefore the program exits with a diagnostic.
Does my computer truly have 140,735 gigabytes of memory? Not a chance! What we see here is a Linux artifact—I am running this program on a computer with a 64-bit Linux operating system—which allocates memory generously in avirtual memory space far beyond what is physically available, in the hope that not all of the requested memory will actually be needed. Under a more conservative operating system the program may have stopped in fewer than 8 iterations since I have only 8 gigabytes of physical memory on this computer.
7.4 The interface and the implementation
Thexmalloc module is needed in almost all of this book’s projects; therefore I expect that every reader will go through this chapter early in their studies. For that reason, I will devote more than the usual amount of attention to details here. In particular, in the following two subsections I will present essentially the entire contents of the filesxmalloc.c andxmalloc.h. You will make a slight adjustment to myxmalloc.cin theProjectssection.
7.4.1 xmalloc.c: The implementation
Listing 7.2 gives a preliminary version of the filexmalloc.cwhich implements ourxmalloc module. It defines a functionmalloc_or_exit()which receives, as its first argument, the number of bytes of memory to be allocated, and as the second and third arguments the file name and line number whence it was invoked. If the allocation is successful, it returns a pointer to the start of the allocated memory; otherwise it prints a message of the sort
foo.c:line 123: malloc() of 10000 bytes failed
50 Chapter 7. Allocating memory:xmalloc()
Listing 7.2:This is a preliminary version of the filexmalloc.c. The functionmalloc_or_exit() attempts to allocatenbytesof memory. If successful, it returns a pointer to that mem- ory; otherwise it prints a message and exits the program. See Part 7.1 of Section 7.5 regarding a subtle flaw and how to fix it.
1 #include <stdio.h>
2 #include "xmalloc.h"
3 void *malloc_or_exit(size_t nbytes, const char *file, int line)
4 {
5 void *x;
6 if ((x = malloc(nbytes)) == NULL) {
7 fprintf(stderr, "%s:line %d: malloc() of %zu bytes failed\n",
8 file, line, nbytes);
9 exit(EXIT_FAILURE);
10 } else
11 return x;
12 }
to thestderrand then callsexit()to quit the program. Note the two occurrences of the void pointer in that listing, and be sure to understand their purpose. Refer to Section 4.3 for an explanation of void pointers.
Here is a typical call tomalloc_or_exit():
double *x;
x = malloc_or_exit(100 * sizeof *x, __FILE__, __LINE__);
... use x ...
free(x);
This allocates a contiguous area of memory capable of holding 100 numbers of the type double. The C preprocessor macros__FILE__and__LINE__take on the file name and the line number of where they are invoked, as noted on page 46.
The call tomalloc_or_exit()may be simplified by noting that although the value passed to its first argument will vary in general from one instance to the next, the second and third arguments are always__FILE__and__LINE__. This motivates the introduc- tion of the preprocessor macro16
#define xmalloc(nbytes) malloc_or_exit((nbytes), __FILE__, __LINE__) which expands the expressionxmalloc(nbytes)into a full-fledged call to the function malloc_or_exit(). The caller’s interface will look like this:
double *x = xmalloc(100 * sizeof *x);
... use x ...
free(x);
and that’s as simple as it’s going to get.
From now on we will call thexmalloc()wrapper instead ofmalloc_or_exit() (ormalloc()) to allocate memory in our programs.
Remark 7.3. The memory fetched bymalloc()can be resized using the standard library’srealloc()function. If a call torealloc()fails, it returnsNULLjust like
16Here I am violating the C tradition of all-capital names for macros. The all-capsXMALLOC, however, looks too ungainly to me; therefore I will break from the tradition in favor of the gentler lowercasexmalloc.
7.4. The interface and the implementation 51
Listing 7.3:The header filexmalloc.hprovides thexmallocmodule’s interface.
1 #ifndef H_XMALLOC_H
2 #define H_XMALLOC_H
3 #include <stdlib.h>
4 void *malloc_or_exit(size_t nbytes, const char *file, int line);
5 #define xmalloc(nbytes) malloc_or_exit((nbytes), __FILE__, __LINE__)
6 #endif /∗H_XMALLOC_H*/
malloc(); therefore for completeness it would be useful to have anxrealloc()to accompanyxmalloc(). However, we don’t have a use forrealloc()in this book;
therefore I will not explore that idea any further.
7.4.2 xmalloc.h: The interface
The header filexmalloc.h, shown in its entirety in Listing 7.3, provides the interface to ourxmallocmodule. A few comments on its structure are in order:
Lines 1, 2, and 6: These are the header file’s so-calledheader guardsor#include guards.
Their purpose is to prevent unintended multiple inclusions of this header file in other units. To illustrate a case of multiple inclusion in the absence of #include guards, consider two header filesheader1.handheader2.hand a program fileprog.c that #includes both of them. If header2.h also #includes header1.h, then header1.hwill be read twice intoprog.c, leading to potential complications.
If a header file is equipped with#include guards, as is ourxmalloc.h, then this is what happens. Whenxmalloc.c #includesxmalloc.h for the first time, line 1 determines that the symbolH_XMALLOC_His undefined; therefore control passes to line 2, whereH_XMALLOC_Hgets defined. The rest ofxmalloc.his read as usual.
Should xmalloc.cattempt to#includexmalloc.hfor a second time, line 1 will determine that the symbolH_XMALLOC_His already defined; therefore control will pass to line 6, thus bypassing the main body ofxmalloc.h.
Every header file should be equipped with #include guards.The guard symbol, which is H_XMALLOC_Hin our case, is arbitrary, but it should be unique within all header files of the entire project. The usual way of creating such a unique symbol is by basing it on the name of the corresponding header file. Since our header file is namedxmalloc.h, any of the choicesXMALLOC,XMALLOC_H,H_XMALLOC_H, or variations thereof, will serve the purpose. The second one is the traditional choice in C. I prefer the third one since the leadingHavoids an (admittedly remote) clash with IEEE’s POSIX standard, which reserves symbol names beginning withEfor its own use.
The comment on line 6 is not required; its sole purpose is to remind one that the
#endifon that line corresponds to the#ifndef H_XMALLOC_Hon line 1. In a short header file such as ourxmalloc.h, this is rather superfluous, but in a header file that spans hundreds of lines, the reminder is a nice touch.
Remark 7.4. It is a common beginner’s mistake to name the#include guardsym- bols with a leading underscore, such as_XMALLOC, mimicking those in the C stan- dard library. The C standard, however, reserves preprocessor symbols that begin
52 Chapter 7. Allocating memory:xmalloc() with an underscorefor its own private use. If you nameyoursymbols with a lead- ing underscore, you run the risk of stepping over your compiler’s internals. That’s very bad. Don’t do it!
Line 3: At first sight it may look odd why we have#include <stdlib.h>inxmal- loc.hwhile#include <stdio.h>inxmalloc.c. The reason for includingstdio.h inxmalloc.cis to provide a prototype forfprintf()that occurs in that file. There is nofprintf()inxmalloc.hand hence no need forstdio.hthere. On the other hand, we includestdlib.hinxmalloc.hbecause it defines the symbolsize_twhich is needed inxmalloc.h.17There is yet another—and not so obvious—minor benefit of includingstdlib.hinxmalloc.h. Programs that callxmalloc()to allocate memory eventually will callfree()to release that memory. When they includexmalloc.h, they receivestdlib.h(which declares the prototype offree()) as a bonus; therefore they need not include it separately.