Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Encapsulating C Memory Allocation


AUG90: ENCAPSULATING C MEMORY ALLOCATION

ENCAPSULATING C MEMORY ALLOCATION

Detect memory allocation errors automatically

This article contains the following executables: SCHIMAND.LST

Jim Schimandle

Jim owns Primary Syncretics, which specializes in the design of hardware and software for embedded, real-time systems. His 10 years of experience runs the gamut from panel switch bootstraps to SPARC data acquisition systems. Jim can be reached at 408-988-3818.


Originally the program was small, less than 20,000 lines, and command-line driven. But after two years, four different programmers, and the addition of a lot of functionality -- a nifty graphical user interface, multiple printer support, foreign language menus, and a kitchen sink -- the program grew to 150,000 lines. Then it started crashing.

The timing of crashes was erratic. Sometimes the program would run for weeks without crashing, at other times it would crash in two days. After a few weeks of debugging, I finally realized that the program was running out of dynamic memory. I had a classic case of memory leakage.

What is Memory Leakage?

When C programmers allocate memory using malloc(), they must be sure to free() the memory when done using it. If the memory is not freed, it "leaks" out of the usable memory pool. Such memory cannot be reused.

Consider the program in Listing One, page 110. This program allocates a list of items until all memory is used up. Then the list of items is freed. If the program is designed properly, it should never exit the while loop in main(). When this program is compiled and run, however, it eventually runs out of memory and exits the while loop.

The problem is found in the junk _close() function. The function correctly frees the JUNK structure, but fails to free the string pointed to by junk _name. (See lines 68-83 and 86-89 of Listing One.)

This Will Never Happen to Me

I hear you cry foul. "This is nothing more than an example of poor programming," you're saying. "This would never happen to a real programmer." Besides, if another programmer looked at the code, it would be obvious where the problem lies.

First, if you look at the revision history in lines 2-8 of Listing One, the genesis of the error is more understandable. The name tag was added by a different programmer after the first release of the module. We can assume the second programmer had a problem, added a quick fix, and then forgot to look into the other effects the change might have on the program.

Secondly, this is a pared-down version of the original 150,000-line application. Finding this error in such a large application is a daunting task. You could look at this code for weeks and probably not notice the lack of a single free() in one function.

Finally, in the original application, the calls to the open and close functions were data dependent. Thus the programming error showed up only under certain data conditions.

Examining Assumptions

Whenever I am faced with a failure in a system, I search for the basic assumptions that lead to the failure. For the standard library memory allocation calls, the underlying assumption that leads to memory leaks is that the caller is responsible for error checking.

This assumption is a direct result of the Unix history of C. The entire Unix philosophy assumes that programmers must handle all errors, at all times, with no exceptions. There is no "big brother" on Unix. You're on your own.

Well, a peon like myself needs a slightly more bullet-proof interface. What I really need is embedded code that can inform me when a problem occurs. Then, when I make a mistake, the mistake will be automatically caught during development.

By encapsulating the memory allocation functions, I can provide a layer of protection between my code and the system library. Such a memory shell intercepts the system library calls and performs basic bookkeeping and error checking.

Designing with a Purpose

Initially, I was interested only in detecting a memory leak. However, the actual memory shell realized multiple design goals:

  • Memory leaks are detected.
  • Manipulation of an invalid memory block is reported.
  • The location within the client code which generates an error is reported.
  • There is minimal execution overhead.
  • The shell does not require massive changes to existing source code.

Junk Revisited

Before we look at the internals of an actual memory shell, let's look at how the shell can be used. Listing Two, page 110, contains a modified version of the junk program in Listing One, with only two changes. The first is the inclusion of the mshell.h interface header (lines 11-13 of Listing Two). The second is the calling of the Mem_Used() function in main(), lines 29-44 of Listing Two.

When the program executes, it produces the output shown in Example 1. Because the program reaches the printf() within the if statement (line 38, Listing Two), the value returned by Mem_Used() must have been non-zero. This indicates that not all allocated memory was freed. Mem_Display() produces the other information in the output. For each block not freed, the amount of memory and the file/line number where the allocation was made is displayed.

Example 1: Output from junk1 shows that there are really two sources for the memory leaks. The first comes from the malloc() of the JUNK structure at line 80. The second comes from the strdup() of the name at line 83.

  *** Memory list not empty ***
  Index  Size  File (Line) - total
                      size 6238
  --------------------------------

  0      6004  junk1.c (80)
  1        26  junk1.c (83)
  2        26  junk1.c (83)
  3        26  junk1.c (83)
  4        26  junk1.c (83)
  5        26  junk1.c (83)
  6        26  junk1.c (83)
  7        26  junk1.c (83)
  8        26  junk1.c (83)
  9        26  junk1.c (83)

You can see that the most common error was the failure to free a 26-byte block. Line 83 in Listing Two shows that this is the string allocated by strdup(). The fix is equally obvious. Simply add a free() of junk_name to junk_close().

However, another error is also revealed. Somehow, a JUNK structure allocated on line 80 has not been freed. This error is harder to determine and requires a reexamination of the logic in junk_open(). If the allocation of the JUNK structure on line 80 succeeds but the allocation of the string on line 83 fails, junk_open() fails to free the JUNK structure before returning a NULL.

The second error would be hard to detect by inspection and is also dependent on the amount of memory allocated by other modules. This type of error can be further masked if the allocation statements in junk_open() are written using a typical C coding style that attempts to run as many assignment statements as possible into a single if conditional, as in Example 2. (Why any rational human being would want to do this is beyond me.) This example demonstrates the utility of a memory shell in tracking down some pernicious bugs.

Example 2: Obtuse coding for the allocation of memory in junk_open() can hide the possibility of a memory leak when the first allocation succeeds and the second allocation fails.

  if (((jnew = (JUNK *) malloc(sizeof (JUNK))) == NULL) ||
      ((jnew->junk_name = strdup(name)) == NULL))
            {
            return NULL ;
            }

Redirecting the Standard Library

How can I simply add an include file and suddenly have error checking of the memory interface? The names of the memory routines have not been changed. Yet somehow, I am able to get C to call my routines and not the standard library.

More Details.

Pointer Alignment

The standard library routines malloc(), realloc(), and calloc() always return a pointer of suitable alignment for an object of any type. Alignment refers to a power of 2 address boundary that a processor "naturally" operates on. This natural boundary is normally related to the bus width or specific processor architecture limitations.

A SPARC processor requires that a long be aligned on a 4-byte boundary. This means that the address for a long must have zeros in the least significant 2 bits. If an attempt is made to load a long from a misaligned address, an alignment fault is detected and the program crashes.

An Intel 80386, however, has no requirements on alignment. You are allowed to load any data type from any address. However, if you attempt to load a long from an address with ones in the least significant 2 bits, the processor must perform two memory loads to get the data. This overhead is unacceptable in most applications. So, although the processor does not enforce alignment, you would be a fool not to take advantage of the bus bandwidth.

To meet the alignment requirement, the size of a memory block header must be rounded up to the nearest alignment boundary. The bytes in the resulting alignment gap cannot be used by either the memory shell or the client code. The alignment gap lies between the memory block header and the client data region. (Refer to Figure 1.)

The memory shell uses ALIGN_SIZE to define the alignment byte boundary required for the largest object that must be aligned. ALIGN_SIZE is used by the RESERVE_SIZE macro, which rounds up the size of the memory header to the next ALIGN_SIZE boundary. The resulting RESERVE_SIZE is used by the pointer conversion macros. Suggested values for ALIGN_SIZE for various processors can be found in Table 1.

-- J.S.

Table 1: The alignment sizes for various processors depend upon system bus width and architecture limitations. Failure to observe the minimum size will cause a processor fault. The suggested size is based on knowledge of the bus width and cache line sizes. Larger alignment sizes can cause larger alignment gaps.

  Processor  Minimum  Suggested
              Size       Size
  -----------------------------

  8088          1          1
  80286         1          2
  80386         1          4
  80386sx       1          2
  68000         2          2
  68020         1          4
  SPARC         8         16
  VAX           1         16

To see how this is accomplished, you must understand that C macros are expanded by a preprocessor before the compilation phase. If a macro is defined with the same name as a standard library function, the macro definition is expanded before compilation. The compiler actually sees the macro expansions, not the standard library calls. Thus, a malloc() call in client code is converted to a mem_alloc() call by the macro malloc in mshell.h (see Listing Three, page 110). To the client, mem_alloc() must provide the same service as the standard library function it replaces. This technique is called "redirection."

For example, in Listing Two the source code call malloc (size of (JUNK)) on line 80 is actually a request for the preprocessor to expand the macro malloc defined in mshell.h. This is expanded by the preprocessor to mem_alloc((size of(JUNK)),"junk1.c",103). Since the C compilation phase sees only the token mem_alloc(), the call is correctly redirected to the memory shell.

In order to use redirection, you must be able to recompile the source code that calls the library. With this caveat, redirection can be implemented for any system or user library. You can even perform redirection without changing any source code by using compiler command-line macro definitions.

I have used redirection to debug code that interfaces with system library routines such as fopen() and fclose(). I often use redirection to check modules written by other programmers.

Other Interface Features

If MEM_WHERE is defined, the redirection macros expand into function calls that contain filename and line information. With this information, error messages can pinpoint the client code that allocated the memory block. The __FILE__and__LINE__macros are not available with all compilers, so turn on MEM_ WHERE only if compiler support is available.

If MEM_LIST is defined, the building of an internal list of memory blocks is enabled. This option is used internally in mshell.c. The resulting list is used for dumping the entire allocated memory list when an error occurs.

The header file also defines two functions not found in the standard library: Mem_Used()and Mem_Display(). These functions are used to check memory usage and display the current memory list, respectively.

The redirection of the standard library occurs only if __ MSHELL__ is not defined. The mshell.c module defines __ MSHELL__, which allows mshell.c to call the standard library.

Memory Block Header

In order to provide added error checking, the memory shell must store additional data for each allocated memory block. Since we have no idea how many memory blocks will be allocated, a static array is insufficient. We could perform a separate malloc() to allocate storage for a control structure every time a new memory block is created. A more efficient method, however, is to allocate a memory block large enough to store both the control structure and the data region. Only the memory shell functions are allowed to access the data in the control structure. Only the memory shell clients are allowed to access the data region.

In the memory shell, the control structure is called the "memory block header." This header is placed before (at lower addresses in relation to) the data region. See Figure 1.

The standard library functions use pointers to the data region. Since this does not correspond to the base of the memory block allocated by the memory shell, we have to convert to and from memory block header pointers and client data region pointers. This is done by using pointer arithmetic and casts. The macros CLIENT_2_HDR and HDR_2_CLIENT handle these pointer conversions (See lines 74-75, Listing Four, page 111.)

Header Fields

The actual fields within the header vary depending on which compilation options are used (see lines 45-57 of Listing Four). At a minimum, tag and size values are required.

The tag is a unique value that identifies this memory block as valid. The tag field is set to the unique value when the header is first initialized. All memory shell routines that manipulate the header first check for the validity of the tag before performing any operations. While there is a finite possibility that the tag value will be valid for an invalid block, the odds are very small.

The size is the number of bytes in the client data region. This value is needed to keep track of the total number of bytes currently allocated by the memory shell.

If MEM_LIST is defined, next and previous links are added to the header. These pointers are used to build a two-way linked list of memory blocks. This list is used for displaying the memory blocks in use.

If MEM_WHERE is defined, a pointer to a filename and a line number are added to the header. These fields are filled in by the last memory shell function to manipulate the block. Note that the filename string is not copied locally. Thus, the filename must be either in static or global storage. This rule is followed by all compilers that I have used that support the __FILE__ feature.

Shell Implementation

The function mem_alloc() receives calls intended for malloc(). First, mem_alloc() allocates a memory block large enough for both the memory block header and the client data region using malloc(). If the allocation fails, a NULL is returned to the client. If the allocation succeeds, the memory header is initialized. If MEM_LIST is enabled, the memory block is added to the memory block list. Finally, the HDR_2_CLIENT macro is used to convert a memory block header pointer to a client data region pointer. The resulting pointer is returned to the client.

Note that this routine does not check for errors. It simply stores enough information to allow for error checking in mem_realloc() and mem_free().

The function mem_free() receives calls intended for free(). First, mem_free() converts the client data region pointer to a memory header pointer using the CLIENT_2_HDR macro. If the tag in the header is invalid, an error is reported. The tag is then complemented to force the tag to be invalid. This allows the detection of a duplicate free() of the same memory block. If MEM_LIST is enabled, the memory block is removed from the memory block list. Finally, the standard library free() function is used to return the memory block to the system.

The function mem_realloc() receives calls intended for realloc(). The logic is a combination of that found in mem_free() and mem_alloc().

The function mem_strdup()receives calls intended for strdup(). In mem_strdup(), a call is made to mem_alloc() to obtain the needed memory. If the call fails, a NULL is returned. Otherwise, the string is copied to the client data region and the string pointer is returned to the client.

Bad memory tags are reported using mem_tag_err(). This function should be modified to suit the needs of your particular application. It currently displays an error message, dumps the memory list, and crashes the program. Not graceful, but acceptable for development.

The detection of a memory leak requires a check immediately before your program exits. If your termination code correctly frees all allocated memory, Mem_Used() should return zero. If not, you can use Mem_Display() to look at the memory blocks that are still allocated. (See lines 36 - 41 in Listing Two.)

The functions mem_list_add() and mem_list_delete() manipulate the two-way linked list of memory blocks. The list manipulation is standard, so I won't dwell on it.

I have not included calloc() in the memory shell because I never use the function. If you wish to use calloc(), the implementation is straightforward. Calculate the size needed, call mem_alloc(), and then clear the data region with memset().

Performance and Space Issues

The memory shell adds overhead both in execution speed and in the amount of memory allocated. In the wide range of applications I have coded, the overhead is not noticeable and has never deterred me from using the shell. If the overhead causes problems in your application, you can conditionally turn the protection features on for development and then disable the more time-and space-consuming features for production.

I tend not to disable any error checking for production. Instead, I conditionally compile the code to dump enough information to allow me to debug the problem when the customer finds that inevitable bug. When I can debug the problem quickly, customer goodwill far outweighs the performance or speed penalties.

Portability

This shell, in various incarnations, has worked on many MS-DOS machines, Sun3s, Sun4s, and VAXs. The portability problems I've encountered are:

  • If your compiler lacks __FILE__ and __LINE__ support, undefine MEM_WHERE.
  • If your compiler does not support ANSI prototypes or #if defined(), you will have to make changes to the code for backward compatibility.
  • If your processor enforces alignment for data types, the value of ALIGN_SIZE may need to be adjusted. See the accompanying text box entitled "Pointer Alignment" for more information.
  • If you need to use system calls directly to take advantage of extra functionality, add a secondary memory shell. This shell provides calls analogous to the standard library calls, but will take advantage of system-specific features. I recently used such a shell on a port from MS-DOS to OS/286. The shell allocated each memory block as a separate LDT entry. This allowed hardware checking for many pointer-related errors. I was amazed at the number of pointer errors that were detected in shipping "bug-free" code.

Variations on a Theme

Since this shell was implemented, I have used it successfully on several projects. On each one, I have made minor modifications to fit the shell into a particular environment. Here are some variations that could be helpful:

  • A global out of memory handler can be installed in malloc() and realloc(). The handler can either crash the program or attempt to recover. I used this on one project and found it of limited utility. Error recovery is best handled by the memory shell clients, not a global error handler. When retrofitted to existing code, however, you can at least find out whether a problem exists.
  • Overwrites of memory can be detected in a limited way by adding a few extra bytes to the start and end of each client data region. If these bytes are filled with magic values, the values can be checked when the block is freed. If a client has written outside data region, the values should be trashed. Note: Don't use either 0x00 or 0xff for your magic values as these are the most common values for "off by one" overwrite errors.
  • A common memory allocation problem is the use of a pointer to memory that has been freed. The pointer looks completely valid for most purposes, but the memory pointed at no longer belongs to the program. The memory remains unchanged in most implementations, however, so the values stored by the client in the memory block will probably be valid until the block is reused by another call to malloc(). This is referred to as using a stale pointer and can cause very erratic program behavior. If you suspect that stale pointers are being used, the memory shell can zero the data blocks in mem_free() immediately before the free(). This way, if a stale pointer is used to access data in a freed block, the data returned is zero. A variation on this is to complement all bytes in the block. By complementing the bytes, you can guarantee that the data in the block is scrambled when accessed through a stale pointer. This should cause your program to blow up quickly.
  • Memory tags are not guaranteed to be unique. There is a finite probability that an arbitrary memory area can contain the expected memory tag value. If you are worried about using memory tags to check for a valid memory block, you can search the memory block list at the places where I check memory tags for validity. If you can't find the block pointer in the list, the block is invalid. Such a list search can take significant execution time, so use this approach only if all else fails.

Roll Your Own

The variations on a memory shell are endless. Every time I build a new application I find one or more improvements. The important thing is to encapsulate the memory routines. Once they are encapsulated, you will have control over the memory allocation and can add the features that your application needs. You will come up with uses that I cannot even imagine. Good luck.

_ENCAPSULATING C MEMORY ALLOCATION_ by Jim Schimandle

[LISTING ONE]

<a name="019e_0017">

/* junk.c  -- Junk list build/destroy test
 *  $Log:   E:/vcs/junk/junk.c_v  $
 *      Rev 1.1   20 Nov 1989 09:42:00   set
 *   Added name field for junk node tagging
 *      Rev 1.0   09 Nov 1989 18:12:30   jvs
 *   Initial revision.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Junk item structure  */
typedef struct jnod {
   struct jnod   *junk_next ;
   char      *junk_name ;
   int      junk_data[3000] ;
   } JUNK ;

/* Function prototypes  */
JUNK   *junk_list_build(void) ;
void   junk_list_destroy(JUNK *) ;
JUNK   *junk_open(char *) ;
void   junk_close(JUNK *) ;

/* main() -- Entry point for test  */
void main()
{
JUNK *jlist ;

while ((jlist = junk_list_build()) != NULL)
   {
   junk_list_destroy(jlist) ;
   }
printf("*** Should never get here! ***\n") ;
}

/* junk_list_build()  -- Build a list of junk items  */
JUNK *junk_list_build()
{
JUNK *jlist ;
JUNK *jnew ;

jlist = NULL ;
while ((jnew = junk_open("name to identify junkitem")) != NULL)
   {
   jnew->junk_next = jlist ;
   jlist = jnew ;
   }

return jlist ;
}

/* junk_list_destroy()  -- Destroy a list of junk items  */
void junk_list_destroy(JUNK *jlist)
{
JUNK *jtmp ;

while (jlist != NULL)
   {
   jtmp = jlist ;
   jlist = jlist->junk_next ;
   junk_close(jtmp) ;
   }
}

/* junk_open() -- Create a junk item  */
JUNK *junk_open(char *name)
{
JUNK *jnew ;

jnew = (JUNK *) malloc(sizeof(JUNK)) ;
if (jnew != NULL)
   {
   jnew->junk_name = strdup(name) ;
   if (jnew->junk_name == NULL)
      {
      return NULL ;
      }
   }

return jnew ;
}

/*  junk_close() -- Close a junk item  */
void junk_close(JUNK *jold)
{
free(jold) ;
}




<a name="019e_0018"><a name="019e_0018">
<a name="019e_0019">
[LISTING TWO]
<a name="019e_0019">

/* junk.c -- Junk list build/destroy test
 *  $Log:   E:/vcs/junk/junk.c_v  $
 *      Rev 1.2   28 Nov 1989 10:13:07   jvs
 *   Addition of memory shell
 *      Rev 1.1   20 Nov 1989 09:42:00   set
 *   Added name field for junk node tagging
 *      Rev 1.0   09 Nov 1989 18:12:30   jvs
 *   Initial revision.
 */

#include <stdio.h>
#include <stdlib.h>
#include "mshell.h"

/* Junk item structure  */
typedef struct jnod {
   struct jnod   *junk_next ;
   char      *junk_name ;
   int      junk_data[3000] ;
   } JUNK ;

/* Function prototypes */
JUNK   *junk_list_build(void) ;
void   junk_list_destroy(JUNK *) ;
JUNK   *junk_open(char *) ;
void   junk_close(JUNK *) ;

/* main() -- Entry point for test  */
void main()
{
JUNK *jlist ;

while ((jlist = junk_list_build()) != NULL)
   {
   junk_list_destroy(jlist) ;
   if (Mem_Used() != 0)
      {
      printf("*** Memory list not empty ***\n") ;
      Mem_Display(stdout) ;
      exit(1) ;
      }
   }
printf("*** Should never get here! ***\n") ;
}

/* junk_list_build() -- Build a list of junk items  */
JUNK *junk_list_build()
{
JUNK *jlist ;
JUNK *jnew ;

jlist = NULL ;
while ((jnew = junk_open("name to identify junkitem")) != NULL)
   {
   jnew->junk_next = jlist ;
   jlist = jnew ;
   }

return jlist ;
}

/* junk_list_destroy() -- Destroy a list of junk items  */
void junk_list_destroy(JUNK *jlist)
{
JUNK *jtmp ;

while (jlist != NULL)
   {
   jtmp = jlist ;
   jlist = jlist->junk_next ;
   junk_close(jtmp) ;
   }
}

/* junk_open() -- Create a junk item  */
JUNK *junk_open(char *name)
{
JUNK *jnew ;

jnew = (JUNK *) malloc(sizeof(JUNK)) ;
if (jnew != NULL)
   {
   jnew->junk_name = strdup(name) ;
   if (jnew->junk_name == NULL)
      {
      return NULL ;
      }
   }

return jnew ;
}

/* junk_close() -- Close a junk item  */
void junk_close(JUNK *jold)
{
free(jold) ;
}



<a name="019e_001a"><a name="019e_001a">
<a name="019e_001b">
[LISTING THREE]
<a name="019e_001b">

/*----------------------------------------------------------------------
 *++
 *  mshell.h -- Dynamic memory handler interface
 *  Description: mshell.h provides the interface definitions for the dynamic
 *  memory handler.
 *  See mshell.c for complete documentation.
 *+-
 *  $Log$
 *--
 */

/* Compilation options */
#define MEM_LIST      /* Build internal list */
#define MEM_WHERE      /* Keep track of memory block source */

/* Interface functions */
unsigned long   Mem_Used(void) ;
void      Mem_Display(FILE *) ;

/* Interface functions to access only through macros */
#if defined(MEM_WHERE)
void      *mem_alloc(size_t, char *, int) ;
void      *mem_realloc(void *, size_t, char *, int) ;
void      mem_free(void *, char *, int) ;
char      *mem_strdup(char *, char *, int) ;
#else
void      *mem_alloc(size_t) ;
void      *mem_realloc(void *, size_t) ;
void      mem_free(void *) ;
char      *mem_strdup(char *) ;
#endif

/* Interface macros */
#if !defined(__MSHELL__)
#if defined(MEM_WHERE)
#define malloc(a)      mem_alloc((a),__FILE__,__LINE__)
#define realloc(a,b)      mem_realloc((a),(b),__FILE__,__LINE__)
#define free(a)         mem_free((a),__FILE__,__LINE__)
#define strdup(a)      mem_strdup((a),__FILE__,__LINE__)
#else
#define malloc(a)      mem_alloc(a)
#define realloc(a, b)      mem_realloc((a),(b))
#define free(a)         mem_free(a)
#define strdup(a)      mem_strdup(a)
#endif
#endif

/*----------------------------------------------------------------------*/




<a name="019e_001c"><a name="019e_001c">
<a name="019e_001d">
[LISTING FOUR]
<a name="019e_001d">

/*----------------------------------------------------------------------
 *++
 *  mshell.c
 *  Memory management utilities
 *
 *  Description
 *
 *   mshell.c contains routines to protect the programmer
 *   from errors in calling memory allocation/free routines.
 *   The programmer must use the memory calls defined
 *   in mshell.h. When these calls are used, the
 *   allocation routines in this module add a data structure
 *   to the top of allocated memory blocks which tags them as
 *   legal memory blocks.
 *
 *   When the free routine is called, the memory block to
 *   be freed is checked for legality tag.  If the block
 *   is not legal, the memory list is dumped to stderr and
 *   the program is terminated.
 *
 *  Compilation Options
 *
 *   MEM_LIST   Link all allocated memory blocks onto
 *         an internal list. The list can be
 *         displayed using Mem_Display().
 *
 *   MEM_WHERE   Save the file/line number of allocated
 *         blocks in the header.
 *         Requires that the compilier supports
 *         __FILE__ and __LINE__ preprocessor
 *         directives.
 *         Also requires that the __FILE__ string
 *         have a static or global scope.
 *
 *+-
 *
 *  $Log$
 *
 *--
 */

#define __MSHELL__

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "mshell.h"

/* Constants */
/* --------- */
#define MEMTAG   0xa55a         /* Value for mh_tag */

/* Structures */
/* ---------- */
typedef struct memnod         /* Memory block header info   */
   {            /* ---------------------------- */
   unsigned int   mh_tag ;   /* Special ident tag      */
   size_t      mh_size ;   /* Size of allocation block   */
#if defined(MEM_LIST)
   struct memnod   *mh_next ;   /* Next memory block      */
   struct memnod   *mh_prev ;   /* Previous memory block   */
#endif
#if defined(MEM_WHERE)
   char      *mh_file ;   /* File allocation was from   */
   unsigned int   mh_line ;   /* Line allocation was from   */
#endif
   } MEMHDR ;

/* Alignment macros */
/* ---------------- */
#define ALIGN_SIZE sizeof(double)
#define HDR_SIZE sizeof(MEMHDR)
#define RESERVE_SIZE (((HDR_SIZE+(ALIGN_SIZE-1))/ALIGN_SIZE) \
         *ALIGN_SIZE)

/* Conversion macros */
/* ----------------- */
#define CLIENT_2_HDR(a) ((MEMHDR *) (((char *) (a)) - RESERVE_SIZE))
#define HDR_2_CLIENT(a) ((void *) (((char *) (a)) + RESERVE_SIZE))

/* Local variables */
/* --------------- */
static unsigned long   mem_size = 0 ;   /* Amount of memory used */
#if defined(MEM_LIST)
static MEMHDR   *memlist = NULL ;   /* List of memory blocks */
#endif

/* Local functions */
/* --------------- */
void   mem_tag_err(void *, char *, int) ;   /* Tag error */
#if defined(MEM_LIST)
void   mem_list_add(MEMHDR *) ;      /* Add block to list */
void   mem_list_delete(MEMHDR *) ;      /* Delete block from list */
#define Mem_Tag_Err(a) mem_tag_err(a,fil,lin)
#else
#define Mem_Tag_Err(a) mem_tag_err(a,__FILE__,__LINE__)
#endif

/************************************************************************/
/**** Functions accessed only through macros ****************************/
/************************************************************************/

/*----------------------------------------------------------------------
 *+
 *  mem_alloc
 *  Allocate a memory block
 *
 *  Usage
 *
 *   void *
 *   mem_alloc(
 *   size_t      size
 *   )
 *
 *  Parameters
 *
 *   size      Size of block in bytes to allocate
 *
 *  Return Value
 *
 *   Pointer to allocated memory block
 *   NULL if not enough memory
 *
 *  Description
 *
 *   mem_alloc() makes a protected call to malloc()
 *
 *  Notes
 *
 *   Access this routine using the malloc() macro in mshell.h
 *
 *-
 */

void *
mem_alloc(
#if defined(MEM_WHERE)
size_t      size,
char      *fil,
int      lin
#else
size_t      size
#endif
)

{
MEMHDR      *p ;

/* Allocate memory block */
/* --------------------- */
p = malloc(RESERVE_SIZE + size) ;
if (p == NULL)
   {
   return NULL ;
   }

/* Init header */
/* ----------- */
p->mh_tag = MEMTAG ;
p->mh_size = size ;
mem_size += size ;
#if defined(MEM_WHERE)
p->mh_file = fil ;
p->mh_line = lin ;
#endif

#if defined(MEM_LIST)
mem_list_add(p) ;
#endif

/* Return pointer to client data */
/* ----------------------------- */
return HDR_2_CLIENT(p) ;
}

/*----------------------------------------------------------------------
 *+
 *  mem_realloc
 *  Reallocate a memory block
 *
 *  Usage
 *
 *   void *
 *   mem_realloc(
 *   void      *ptr,
 *   size_t       size
 *   )
 *
 *  Parameters
 *
 *   ptr      Pointer to current block
 *   size      Size to adjust block to
 *
 *  Return Value
 *
 *   Pointer to new memory block
 *   NULL if memory cannot be reallocated
 *
 *  Description
 *
 *   mem_realloc() makes a protected call to realloc().
 *
 *  Notes
 *
 *   Access this routine using the realloc() macro in mshell.h
 *
 *-
 */

void *
mem_realloc(
#if defined(MEM_WHERE)
void      *ptr,
size_t      size,
char      *fil,
int      lin
#else
void      *ptr,
size_t      size
#endif
)

{
MEMHDR      *p ;

/* Convert client pointer to header pointer */
/* ---------------------------------------- */
p = CLIENT_2_HDR(ptr) ;

/* Check for valid block */
/* --------------------- */
if (p->mh_tag != MEMTAG)
   {
   Mem_Tag_Err(p) ;
   return NULL ;
   }

/* Invalidate header */
/* ----------------- */
p->mh_tag = ~MEMTAG ;
mem_size -= p->mh_size ;

#if defined(MEM_WHERE)
mem_list_delete(p) ;   /* Remove block from list */
#endif

/* Reallocate memory block */
/* ----------------------- */
p = (MEMHDR *) realloc(p, RESERVE_SIZE + size) ;
if (p == NULL)
   {
   return NULL ;
   }

/* Update header */
/* ------------- */
p->mh_tag = MEMTAG ;
p->mh_size = size ;
mem_size += size ;
#if defined(MEM_LIST)
p->mh_file = fil ;
p->mh_line = lin ;
#endif

#if defined(MEM_WHERE)
mem_list_add(p) ;   /* Add block to list */
#endif

/* Return pointer to client data */
/* ----------------------------- */
return HDR_2_CLIENT(p) ;
}

/*----------------------------------------------------------------------
 *+
 *  mem_strdup
 *  Save a string in dynamic memory
 *
 *  Usage
 *
 *   char *
 *   mem_strdup(
 *   char      *str
 *   )
 *
 *  Parameters
 *
 *   str      String to save
 *
 *  Return Value
 *
 *   Pointer to allocated string
 *   NULL if not enough memory
 *
 *  Description
 *
 *   mem_strdup() saves the specified string in dynamic memory.
 *
 *  Notes
 *
 *   Access this routine using the strdup() macro in mshell.h
 *
 *-
 */

char *
mem_strdup(
#if defined(MEM_WHERE)
char      *str,
char      *fil,
int      lin
#else
char      *str
#endif
)

{
char * s ;

#if defined(MEM_WHERE)
s = mem_alloc(strlen(str)+1, fil, lin) ;
#else
s = mem_alloc(strlen(str)+1) ;
#endif

if (s != NULL)
   {
   strcpy(s, str) ;
   }

return s ;
}

/*----------------------------------------------------------------------
 *+
 *  mem_free
 *  Free a memory block
 *
 *  Usage
 *
 *   void
 *   mem_free(
 *   void      *ptr
 *   )
 *
 *  Parameters
 *
 *   ptr      Pointer to memory to free
 *
 *  Return Value
 *
 *   None
 *
 *  Description
 *
 *   mem_free() frees the specified memory block. The
 *   block must be allocated using mem_alloc(), mem_realloc()
 *   or mem_strdup().
 *
 *  Notes
 *
 *   Access this routine using the free() macro in mshell.h
 *
 *-
 */

void
mem_free(
#if defined(MEM_WHERE)
void      *ptr,
char      *fil,
int      lin
#else
void      *ptr
#endif
)

{
MEMHDR       *p ;

/* Convert client pointer to header pointer */
/* ---------------------------------------- */
p = CLIENT_2_HDR(ptr) ;

/* Check for valid block */
/* --------------------- */
if (p->mh_tag != MEMTAG)
   {
   Mem_Tag_Err(p) ;
   return ;
   }

/* Invalidate header */
/* ----------------- */
p->mh_tag = ~MEMTAG ;
mem_size -= p->mh_size ;

#if defined(MEM_LIST)
mem_list_delete(p) ;   /* Remove block from list */
#endif

/* Free memory block */
/* ----------------- */
free(p) ;
}

/************************************************************************/
/**** Functions accessed directly ***************************************/
/************************************************************************/

/*----------------------------------------------------------------------
 *+
 *  Mem_Used
 *  Return amount of memory currently allocated
 *
 *  Usage
 *
 *   unsigned long
 *   Mem_Used(
 *   )
 *
 *  Parameters
 *
 *   None.
 *
 *  Description
 *
 *   Mem_Used() returns the number of bytes currently allocated
 *   using the memory management system. The value returned is
 *   simply the sum of the size requests to allocation routines.
 *   It does not reflect any overhead required by the memory
 *   management system.
 *
 *  Notes
 *
 *   None
 *
 *-
 */

unsigned long
Mem_Used(
void)

{
return mem_size ;
}

/*----------------------------------------------------------------------
 *+
 *  Mem_Display
 *  Display memory allocation list
 *
 *  Usage
 *
 *   void
 *   Mem_Display(
 *   FILE      *fp
 *   )
 *
 *  Parameters
 *
 *   fp      File to output data to
 *
 *  Description
 *
 *   Mem_Display() displays the contents of the memory
 *   allocation list.
 *
 *   This function is a no-op if MEM_LIST is not defined.
 *
 *  Notes
 *
 *   None
 *
 *-
 */

void
Mem_Display(
FILE      *fp
)

{
#if defined(MEM_LIST)
MEMHDR      *p ;
int      idx ;

#if defined(MEM_WHERE)
fprintf(fp, "Index   Size  File(Line) - total size %lu\n", mem_size) ;
#else
fprintf(fp, "Index   Size - total size %lu\n", mem_size) ;
#endif

idx = 0 ;
p = memlist ;
while (p != NULL)
   {
   fprintf(fp, "%-5d %6u", idx++, p->mh_size) ;
#if defined(MEM_WHERE)
   fprintf(fp, "  %s(%d)", p->mh_file, p->mh_line) ;
#endif
   if (p->mh_tag != MEMTAG)
      {
      fprintf(fp, " INVALID") ;
      }
   fprintf(fp, "\n") ;
   p = p->mh_next ;
   }
#else
fprintf(fp, "Memory list not compiled (MEM_LIST not defined)\n") ;
#endif
}

/************************************************************************/
/**** Memory list manipulation functions ********************************/
/************************************************************************/

/*
 * mem_list_add()
 * Add block to list
 */

#if defined(MEM_LIST)
static void
mem_list_add(
MEMHDR   *p
)

{
p->mh_next = memlist ;
p->mh_prev = NULL ;
if (memlist != NULL)
   {
   memlist->mh_prev = p ;
   }
memlist = p ;

#if defined(DEBUG_LIST)
printf("mem_list_add()\n") ;
Mem_Display(stdout) ;
#endif
}
#endif

/*----------------------------------------------------------------------*/

/*
 * mem_list_delete()
 * Delete block from list
 */

#if defined(MEM_LIST)
static void
mem_list_delete(
MEMHDR   *p
)

{
if (p->mh_next != NULL)
   {
   p->mh_next->mh_prev = p->mh_prev ;
   }
if (p->mh_prev != NULL)
   {
   p->mh_prev->mh_next = p->mh_next ;
   }
    else
   {
   memlist = p->mh_next ;
   }

#if defined(DEBUG_LIST)
printf("mem_list_delete()\n") ;
Mem_Display(stdout) ;
#endif
}
#endif

/************************************************************************/
/**** Error display *****************************************************/
/************************************************************************/

/*
 *  mem_tag_err()
 *  Display memory tag error
 */

static void
mem_tag_err(
void      *p,
char      *fil,
int      lin
)

{
fprintf(stderr, "Memory tag error - %p - %s(%d)\n", p, fil, lin) ;
#if defined(MEM_LIST)
Mem_Display(stderr) ;
#endif
exit(1) ;
}

/*----------------------------------------------------------------------*/





[Example 1: Output from junk1 shows that there are really
two sources for the memory leaks. The first comes from the
malloc() of the JUNK structure at line 80. The second comes from
the strdup() of the name at line 83.]

*** Memory list not empty ***
Index   Size  File(Line) - total size 6238
0       6004  junk1.c(80)
1         26  junk1.c(83)
2         26  junk1.c(83)
3         26  junk1.c(83)
4         26  junk1.c(83)
5         26  junk1.c(83)
6         26  junk1.c(83)
7         26  junk1.c(83)
8         26  junk1.c(83)
9         26  junk1.c(83)


[Example 2: Obtuse coding for the allocation of memory in
junk_open() can hide the possibility of a memory leak when the
first allocation succeeds and the second allocation fails.]


if (((jnew = (JUNK *) malloc(sizeof(JUNK))) == NULL) ||
    ((jnew->junk_name = strdup(name)) == NULL))
   {
   return NULL ;
   }










Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.