By Mukesh Kapoor, Sun Microsystems - Sun ONE Studio Solaris Tools Development Engineering |
|
This article discusses some of the runtime errors related to memory management and how you can use the garbage collection library, libgc to fix these errors. In most cases, just linking with the library without making any changes to your code will fix the errors. You can get additional benefits by calling the functions available in the public Application Programming Interface (API) of the library. The library has a C API so you can use it with both C and C++ programs. libgc is included with the Sun C++ compiler product. |
Contents
- |
What Is Garbage Collection? |
- |
What Does libgc Do? |
- |
Common Memory Problems |
- |
libgc API |
- |
Modes of Operation |
- |
Summary |
- |
For More Information |
What Is Garbage Collection?
Garbage collection deals with the automatic management of dynamic memory. You can dynamically allocate and deallocate memory by using the C++ operators new and delete as well as by using the libc functions malloc() and free(). Managing dynamic memory is a complex and highly error prone task and is one of the most common causes of runtime errors. Such errors can cause intermittent program failures that are difficult to find and fix.
A garbage collector automatically analyzes the program's data and decides whether memory is in use and when to return it to the system. When it identifies memory that is no longer in use, it frees that memory.
The libgc library relieves you from the burden of explicit memory management. In addition, libgc automatically fixes memory related problems in third party libraries used in the application.
What Does libgc Do?
The libgc library is a conservative garbage collector that periodically scans a program's data and marks all heap objects that are in use. Once it has marked all heap objects that are reachable through pointers, it frees all the objects that have not been marked. It decides when to collect garbage by monitoring the amount of memory allocated since the last collection. When enough memory has been allocated, it performs the garbage collection.
This way, programs that don't use a lot of dynamic memory won't spend much time in unnecessary garbage collection. The library has its own efficient memory allocator.
Note-
Do not use an outside allocator with libgc. The library provides the best performance with its own allocator. It will not automatically manage memory allocated by other allocators. If you use your own allocator, libgc will not search that memory for pointers to garbage collected memory.
|
Common Memory Problems
Using libgc fixes problems related to memory management in these broad categories:
- Memory leaks
- Premature frees
- Memory fragmentation
libgc does not, however, detect or fix problems due to memory overwrites.
Memory Leaks
This problem occurs if a program fails to free objects that are no longer in use. The following code leaks 100 bytes of memory every time the function test is called with the argument flag as false:
void read_file(char*);
void test(bool flag)
{
char* buf = new char[100];
if (flag) {
read_file(buf);
delete [] buf;
}
}
If a program continues to leak memory, its performance degrades. Its runtime memory footprint continues to increase and it spends more and more time in swapping and can eventually run out of memory.
Premature Frees
This is caused by freeing objects that are still in use, and can result in corrupted data and intermittent failures, including core dumps. Once memory is free, it becomes part of the system heap and can be reallocated later on for use in some other part of the program. If you try to access memory that has already been freed, you may end up accessing data in another, completely unrelated, part of the program. The memory corruption can show up at a time and place farther away from the actual point of error and can be very difficult to debug.
The following code illustrates the problem:
void eval(int*);
void test2()
{
int* p1 = new int;
int* p2 = p1;
*p1 = 1;
eval(p1);
delete p1;
*p2 = 2; // the memory pointed to by p2 has already been deleted!
eval(p2);
}
A variation of this is the case where the same memory pointer is deleted more than once:
void test3()
{
int* p1 = new int;
int* p2 = p1;
delete p1;
delete p2; // deleting again!
}
Memory Fragmentation
This can be caused by frequent allocation and deallocation of memory and can degrade an application's performance. Memory fragmentation occurs when a large chunk of memory is divided into much smaller, scattered pieces. These smaller discontiguous chunks of memory may not be of much use to the program and may never be allocated again. This can result in the program consuming more memory than it actually allocates.
Memory Overwrites
This problem occurs when too little memory is allocated for an object. Consequences can include memory corruption and intermittent failures. The program may work correctly some times and fail erratically at other times. Here is sample code that shows this problem:
void test4()
{
char* x = new char[10];
x[10] = '\0'; // memory overwrite!
}
libgc does not detect or fix this type of memory problem. You can use the Run Time Checking (RTC) feature in the debugger to find such errors.
libgc API
The libgc library has its own implementation of the C memory management functions malloc(), calloc(), realloc() and free(). The library also provides the following three functions that can be called from a user program. You will need to include the header <gct.h> if you use any of these functions in your program. You can use any of the functions listed below in the library's development mode. See the following section for an explanation of libgc modes.
void gcFixPrematureFrees(void)
Fix premature frees. After this function is called, calls to free() will not do anything. The pointer passed as argument to free() will not be freed. The pointer will safely be freed later on when it is no longer in use.
Note-
Fixing premature frees may increase memory usage in some programs because the library defers the release of memory until it verifies that the memory is not being used. This is not an issue for most programs but may be important for some programs with strict memory constraints.
|
void gcStopFixingPrematureFrees(void)
Stop fixing premature frees. This reverses the effect of calling gcFixPrematureFrees.
void gcInitialize(void)
This function lets you configure your own settings and it works slightly differently than the other public API described above. The difference is that the libgc library calls gcInitialize at startup time so you should not call gcInitialize directly in your program. You can define your own function named gcInitialize in your program as shown in the following example:
#include <gct.h>
. . .
void gcInitialize(void)
{
gcFixPrematureFrees();
}
Modes of Operation
The library has two different modes of operation. In each mode, you can use the libgc library by specifying the option CC-library=gc or by specifying -lgc at link time. The -library=gc option is only available with the C++ compiler.
Deployment Mode
You can use this mode when you do not wish to modify your existing code or if you don't have access to the source code and can't modify it. To use libgc in deployment mode, link your application with the library and it automatically fixes memory leaks and memory fragmentation. It also speeds up memory allocation because it uses its own efficient malloc() function.
Development Mode
Development mode provides all the benefits of the deployment mode. In addition, you can write programs without ever calling delete or free(). The library takes care of freeing memory that is not in use. This feature provides one of the benefits of the Java programming language to C and C++ language programmers. If you are using delete or free() in your program, you can also fix premature frees in specific regions of your code by calling the functions gcFixPrematureFrees() and gcStopFixingPrematureFrees().
Summary
Using libgc automatically provides the following benefits in the user application:
- Protects your program against memory leaks.
Even if the programs use leaky third-party libraries, linking with libgc automatically fixes the leaks.
- Allows you to write programs without calling delete or
free(). This is a benefit of the Java programming environment which you can exploit for C and C++ programs.
- Allows you to fix premature frees in your code.
- Provides a fast non-fragmenting memory allocator. Programs linked
with libgc run faster, use less space, and never fragment memory.
- |
Richard Jones and Rafael D Lins, Garbage collection, Algorithms for Automatic Dynamic Memory Management, Wiley, 1996 |
- |
Bjarne Stroustrup, Letter to the ANSI/ISO C++ committee, May 27, 1996. |
About the Author
Mukesh Kapoor is a staff engineer in the C++ compiler group. He has an MS (Automation), BS (Electronics and Communication) from the Indian Institute of Science and BS (Physics) from Delhi University, India. Before joining Sun, Mukesh held various software development roles at Peritus Inc, Plexus computers, Elxsi and Unisoft Corp.
|