Best Practices - Memory Management in C and C++

Similar documents
Memory Allocation. Static Allocation. Dynamic Allocation. Memory Management. Dynamic Allocation. Dynamic Storage Allocation

Object Oriented Software Design II

Lecture 11 Doubly Linked Lists & Array of Linked Lists. Doubly Linked Lists

Lecture 10: Dynamic Memory Allocation 1: Into the jaws of malloc()

Get the Better of Memory Leaks with Valgrind Whitepaper

Chapter 5 Names, Bindings, Type Checking, and Scopes

C++ INTERVIEW QUESTIONS

Leak Check Version 2.1 for Linux TM

Time Limit: X Flags: -std=gnu99 -w -O2 -fomitframe-pointer. Time Limit: X. Flags: -std=c++0x -w -O2 -fomit-frame-pointer - lm

Memory management. Announcements. Safe user input. Function pointers. Uses of function pointers. Function pointer example

Embedded Programming in C/C++: Lesson-1: Programming Elements and Programming in C

Oracle Solaris Studio Code Analyzer

Organization of Programming Languages CS320/520N. Lecture 05. Razvan C. Bunescu School of Electrical Engineering and Computer Science

The C Programming Language course syllabus associate level

Storage Classes CS 110B - Rule Storage Classes Page 18-1 \handouts\storclas

1 Description of The Simpletron

6.088 Intro to C/C++ Day 4: Object-oriented programming in C++ Eunsuk Kang and Jean Yang

Embedded Systems. Review of ANSI C Topics. A Review of ANSI C and Considerations for Embedded C Programming. Basic features of C

A deeper look at Inline functions

Lecture Data Types and Types of a Language

Application Note C++ Debugging

1 Abstract Data Types Information Hiding

Monitoring, Tracing, Debugging (Under Construction)

1) The postfix expression for the infix expression A+B*(C+D)/F+D*E is ABCD+*F/DE*++

Java's garbage-collected heap

vector vec double # in # cl in ude <s ude tdexcept> tdexcept> // std::ou std t_of ::ou _range t_of class class V Vector { ector {

C++ Programming Language

PART-A Questions. 2. How does an enumerated statement differ from a typedef statement?

C++FA 3.1 OPTIMIZING C++

Data Structures Using C++ 2E. Chapter 5 Linked Lists

Node-Based Structures Linked Lists: Implementation

ECS 165B: Database System Implementa6on Lecture 2

No no-argument constructor. No default constructor found

Java Interview Questions and Answers

1 The Java Virtual Machine

10CS35: Data Structures Using C

CS 111 Classes I 1. Software Organization View to this point:


Programming Embedded Systems

CS Computer Security Thirteenth topic: System attacks. defenses

Virtual Servers. Virtual machines. Virtualization. Design of IBM s VM. Virtual machine systems can give everyone the OS (and hardware) that they want.

Trace-Based and Sample-Based Profiling in Rational Application Developer

Name: Class: Date: 9. The compiler ignores all comments they are there strictly for the convenience of anyone reading the program.

Stack Allocation. Run-Time Data Structures. Static Structures

Semantic Analysis: Types and Type Checking

How To Port A Program To Dynamic C (C) (C-Based) (Program) (For A Non Portable Program) (Un Portable) (Permanent) (Non Portable) C-Based (Programs) (Powerpoint)

PROBLEM SOLVING SEVENTH EDITION WALTER SAVITCH UNIVERSITY OF CALIFORNIA, SAN DIEGO CONTRIBUTOR KENRICK MOCK UNIVERSITY OF ALASKA, ANCHORAGE PEARSON

Coding conventions and C++-style

Short Notes on Dynamic Memory Allocation, Pointer and Data Structure

Instrumentation Software Profiling

First Java Programs. V. Paúl Pauca. CSC 111D Fall, Department of Computer Science Wake Forest University. Introduction to Computer Science

C++ Class Library Data Management for Scientific Visualization

Designing with Exceptions. CSE219, Computer Science III Stony Brook University

Basic Unix/Linux 1. Software Testing Interview Prep

language 1 (source) compiler language 2 (target) Figure 1: Compiling a program

6.S096 Lecture 1 Introduction to C

Registry Tuner. Software Manual

COMP 356 Programming Language Structures Notes for Chapter 10 of Concepts of Programming Languages Implementing Subprograms.

Module 10. Coding and Testing. Version 2 CSE IIT, Kharagpur

KITES TECHNOLOGY COURSE MODULE (C, C++, DS)

Applying Clang Static Analyzer to Linux Kernel

Comp151. Definitions & Declarations

C Programming Review & Productivity Tools

How to test and debug an ASP.NET application

Eliminate Memory Errors and Improve Program Stability

Format string exploitation on windows Using Immunity Debugger / Python. By Abysssec Inc

Jonathan Worthington Scarborough Linux User Group

CS 241 Data Organization Coding Standards

Objectives. At the end of this chapter students should be able to:

NUMBER SYSTEMS APPENDIX D. You will learn about the following in this appendix:

Performance Improvement In Java Application

Linux Driver Devices. Why, When, Which, How?

The programming language C. sws1 1

UIL Computer Science for Dummies by Jake Warren and works from Mr. Fleming

16 Collection Classes

Avira System Speedup. HowTo

Bachelors of Computer Application Programming Principle & Algorithm (BCA-S102T)

A Comparison of Programming Languages for Graphical User Interface Programming

GDB Tutorial. A Walkthrough with Examples. CMSC Spring Last modified March 22, GDB Tutorial

Interpreters and virtual machines. Interpreters. Interpreters. Why interpreters? Tree-based interpreters. Text-based interpreters

Object-Oriented Design Lecture 4 CSU 370 Fall 2007 (Pucella) Tuesday, Sep 18, 2007

Fast Arithmetic Coding (FastAC) Implementations

CS193D Handout 06 Winter 2004 January 26, 2004 Copy Constructor and operator=

Arrays. Atul Prakash Readings: Chapter 10, Downey Sun s Java tutorial on Arrays:

Common Data Structures

Lecture 22: C Programming 4 Embedded Systems

DISK DEFRAG Professional

winhex Disk Editor, RAM Editor PRESENTED BY: OMAR ZYADAT and LOAI HATTAR

Polymorphism. Problems with switch statement. Solution - use virtual functions (polymorphism) Polymorphism

Debugging. Common Semantic Errors ESE112. Java Library. It is highly unlikely that you will write code that will work on the first go

An Incomplete C++ Primer. University of Wyoming MA 5310

Minimizing code defects to improve software quality and lower development costs.

Chapter 8 Software Testing

7.1 Our Current Model

CISC 181 Project 3 Designing Classes for Bank Accounts

Object Oriented Software Design II

Simple C Programs. Goals for this Lecture. Help you learn about:

Transcription:

Best Practices - Memory Management in C and C++ By Shilpi Gupta Agarwal Abstract C and C++ programmers very often allocate and de-allocate memory on the heap without the proper understanding on how these low-level facilities work and what happens underneath. "Pay no attention to the man behind the curtain." The Wizard of Oz But these memory related problems becomes a great concern in the systems with shortage of almost all the resources including memory, like embedded real-time systems. This dynamic behavior tends to be non-deterministic and the failure is hard to contain. Similarly memory allocation failure on such systems can be fatal. Unlike a desktop application, most embedded systems do not have the opportunity to pop up a dialog and discuss options with the user. Often, resetting is the only option, which is unattractive. Hence, this paper attempts to discuss the strategies to achieve clean code and appropriate memory management. Introduction Memory management is the process of recognizing when allocated objects are no longer needed, deallocating (freeing) the memory used by such objects, and making it available for subsequent allocations. In C and C++ programming languages, memory management is programmer s responsibility. The complexity of this task leads to many common errors that can cause unexpected or erroneous program behavior and crashes. As a result, a large proportion of developer time is often spent debugging and trying to correct such errors. The newer languages (Java, Python, and Perl) have automated memory management. This ranges from Python s simple reference-counted model to Java s sophisticated garbage collector. 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. This automatic memory management enables increased abstraction of interfaces and more reliable code. To the programmer, however, the final result is nearly always the same: no more worrying about memory errors. In order to ease up C/C++ programmer s life, many memory errors detection tools are available in the market such as Valgrind, Electric Fence, and Purify. These tools can be used to detect some memory defects, but these cannot guarantee their absence. For the ones who do not have such a tool or don't want to use, there are two strategies available to the programmer. The first strategy must always be to architect memory errors out of your system. The second is the old-school way: code inspection. To restrict the search in order to speed up the debugging process, one should first review the pieces of code with a dense use of pointers and casts.

Memory Layout The basic process memory layout is the result of "linking" (combining the various.o and.a (or.so) files into an executable program format) plus arranging the disk file's data into main memory (allocating data storage and such). Text Segment: The text segment contains the actual code to be executed. It's usually sharable, so multiple instances of a program can share the text segment to lower memory requirements. This segment is usually marked read-only so a program can't modify its own instructions. Initialized Data Segment: This segment contains global variables which are initialized by the programmer. For example, the C declaration int maxcount = 99; Uninitialized Data Segment: Also named "bss" (block started by symbol) which was an operator used by an old assembler. This segment contains uninitialized global variables. All variables in this segment are initialized to 0 or NULL pointers before the program begins to execute. The C declaration

long sum[1000]; appearing outside any function causes this variable to be stored in the uninitialized data segment. The stack: The stack is a collection of stack frames. Every time a function is called, an area of memory is set aside, called a stack frame, for the new function call. This area of memory holds some crucial information, like: 1. Storage space for all the automatic variables for the newly called function. 2. The line number of the calling function to return to when the called function returns. 3. The arguments, or parameters, of the called function. The heap: Most dynamic memory, whether requested via C's malloc() and friends or C++'s new is doled out to the program from the heap. The C library also gets dynamic memory for its own personal workspace from the heap as well. As more memory is requested "on the fly", the heap grows as per requirement. Size() function The GNU `size' utility lists the section sizes(text, data, and bss segments) for each object files to be examined. 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 a0cb6 /bin/sh The fourth and fifth columns are the total of the three sizes, displayed in decimal and hexadecimal, respectively.

Memory Problems Taxonomy Typically, the following four kinds of memory problems are encountered by developers: 1. Memory leaks Memory leaks occur when a block of memory is allocated, but it is never released back to the system after it is no longer needed. If enough leaks occur, they can keep consuming memory until all available memory is exhausted and when memory leaks are serious, applications can be terminated due to a shortage of memory. This type of problem is hard to locate. int f(void) char* p; p = (char *) malloc(8 * sizeof(char)); return 0; int main(void) f(); An easy way to find out if your code is leaking memory is by executing it and examining its memory usage either using Task Manager on Windows or top on Linux. Best Practices: If you intend others to inherit from your class, you must declare its destructor virtual. Make sure that the default methods that C++ generates work well with your design. A good approach to protect from memory leaks is to use a garbage collector and replace new with its allocator. There are a lot of libraries which can provide good garbage collectors, or one can implement their own. Another choice can be to use private heaps, that is, you first allocate a big chunk of memory and manage it in a Heap class (whenever you need new memory to be allocated, a request is being made to the Heap class). Instead of deleting every chunk you allocate for every object, you can delete the whole heap when the objects using it are no longer needed. This can also be used just for backup. In case you forget to delete some object you can be sure that by deleting the Heap there will be no memory leaks remaining. Smart pointers are another efficient way of fighting memory leaks. They are often used to manage the lifetimes of other dynamically allocated objects.

A smart pointer type is defined as any class type that overloads operator->, operator*, or operator->* and overloading these operators allows a smart pointer to behave much like a built-in pointer. One smart pointer provided by the standard library (STL) is called auto_ptr and it is declared in the <memory> header. The following piece of code illustrates its usage: void f() Dialog *dl = new Dialog(); auto_ptr pdl = dl; At the end of the function the auto_ptr object will go out of scope and will delete automatically the memory allocated in the first statement. This above technique is used when there are some exceptions thrown between allocating and deallocating memory and you want to make sure there aren't going to be any memory leaks. 2. Duplicate release or Double Free of memory When a block of memory is deleted or deallocated and the pointer to that block of memory is not modified, now if this same pointer is used to delete the memory again then the result of the second deletion is unpredictable. Sometimes this silent corruption of memory data can lead to the bugs that are extremely hard to solve. int* p; p = new int;.. delete p; Another kind of problem also exists class Contact char* name; int age; public: Contact( const char* inname, inage ) name = new char[strlen( inname ) + 1]; strcpy( name, inname ); age = inage; ~Contact() delete[] name; ;

Contact c1("fred", 40 ); Contact c2 = c1; The problem here is- c1 and c2 will have the same pointer value for the "name" field. When c2 goes out of scope, its destructor will get called and delete the memory that was allocated when c1 was constructed (because the name field of both objects have the same pointer value). Then, when c1 destructs, it will attempt to delete the pointer value, and a "double-free" occur. At best, the heap will catch the problem and report an error. At worst, the same pointer value may, by then, be allocated to another object, the delete will free the wrong memory, and this will introduce a difficult-to-find bug in the code. Best Practices: Assign NULL to a pointer after freeing (or deleting) it. This prevents the program from crashing should the pointer be accidentally freed again. Calling free or delete on NULL pointers is guaranteed not to cause a problem. Write your own copy constructors and assignment operators that will work correctly with your classes. 3. Accessing memory incorrectly Incorrect memory access includes accessing - a memory address that has been released (also known as dangling reference) It is possible to deallocate the space used by an object to which some other object still has a reference. If the object with that (dangling) reference tries to access the original object, but the space has been reallocated to a new object, the result is unpredictable and not what was intended. char *a = malloc(128*sizeof(char)); char *b = malloc(128*sizeof(char)); b = a; free(a); free(b); // will not free the pointer to the original allocated memory - a local pointer variable not initialized (i.e., it then points at some random location), or by setting the pointer to an incorrect value., or - Reading/writing to memory out of the bounds of a static array. char *f(void) char p[8]; return p; int main(void)

char *res = f(); Best Practices: Add an explicit copy constructor and an assignment operator to the class or struct which contains pointer fields. Disable copying and assignment altogether by making the copy constructor and assignment operator private. Modify your classes so that the default copy constructor and assignment operator are correct (by using member objects instead of dynamic allocation, or certain types of smart pointers such as the shared_ptr from Boost.org). 4. Memory allocation failure If there is no memory available or if the program has exceeded the amount of memory it is allowed to reference, malloc function call will return a null pointer, which should always be checked for after allocation. Many programs do not check for malloc failure. Such a program would attempt to use the null pointer returned by malloc as if it pointed to allocated memory, and the program would crash. Memory allocation may also fail when the requested memory block is not available in contiguos address space. This situation is often reffered as fragmentation. The best way to understand memory fragmentation is to look at an example. For this example, it is assumed that there is a 10K heap. First, an area of 3K is requested, thus: #define K (1024) char *p1; p1 = malloc(3*k); Then, a further 4K is requested: p2 = malloc(4*k); The resulting situation is shown in Figure 2 3K of memory is now free. Sometime later, the first memory allocation, pointed to by p1, is deallocated: free(p1); This leaves 6K of memory free in two 3K chunks, as illustrated in Figure 3. A further request for a 4K allocation is issued: p1 = malloc(4*k);

This results in a failure - NULL is returned into p1 - because, even though 6K of memory is available, there is not a 4K contiguous block available. This is memory fragmentation. It would seem that an obvious solution would be to de-fragment the memory, merging the two 3K blocks to make a single one of 6K. However, this is not possible because it would entail moving the 4K block to which p2 points. Moving it would change its address, so any code that has taken a copy of the pointer would then be broken. In other languages (such as Visual Basic, Java and C#), there are defragmentation (or garbage collection ) facilities. This is only possible because these languages do not support direct pointers, so moving the data has no adverse effect upon application code. This defragmentation may occur when a memory allocation fails or there may be a periodic garbage collection process that is run. In either case, this would severely compromise real time performance and determinism. Best practices: When using new, enclose it within a try-catch block. The new operator throws an exception and does not return a value. To force the new operator to return a value, use the nothrow qualifier as shown below: int * pt = new (std::nothrow) int [100]; Using an array member will be cleaner (more succinct, less error prone) and faster as there is no need to call allocation and deallocation functions. float x[6]; vs. float *x; // in class definition x = new float[6]; // in class constructor delete [] x; // in class destructor Other Generic Best practices in C/C++ When storing secrets such as passwords in memory, overwrite them with random data before deleting them. Need to note that free and delete merely make previously allocated memory unavailable, they don't really 'delete' data contained in that memory. Do not allocate and deallocate memory in a loop as this may slow down the program and may sometime cause security malfunctions.

Use unsigned integer types to hold the number of bytes to be allocated, when allocating memory dynamically. This weeds out negative numbers. Also check the length of memory allocated against a maximum value. When using C and C++ code together, if new has been used to allocate memory, use delete to free it. Do not use free. Likewise, if malloc or calloc has been used to allocate memory, use free when deallocating. Do not use delete. Common Memory Problems Here are some memory problems that frequently occur in every developer s life and what does each problem imply. Symptom random failures, especially on call return calls to malloc/free fail program magically works if printf inserted Implies Corrupted the stack frame return info Corrupted the malloc bookkeeping data Corrupted storage space in stack frame Debugging Memory Techniques There are some simple techniques that can used to debug crash issues: 1. Obtain Stack Dump Make sure that every embedded processor in the system supports dumping of the stack at the time of crash. The crash dump should be saved in non volatile memory so that it can be retrieved by tools on processor reboot. In fact attempt should be made to save as much as possible of processor state and key data structures at the time of crash. 2. Using assert An ounce of prevention is better than a pound of cure. Detecting crash causing conditions by using assert macro can be a very useful tool in detecting problems much before they lead to a crash. Basically assert macros check for a condition which the function assumes to be true. For example, the code below shows an assertion which checks that the message to be processed is non NULL. During initial debugging of the system this assert condition might help you detect the condition, before it leads to a crash. Note that asserts do not have any overhead in the shipped system as in the release builds asserts are defined to a NULL macro, effectively removing all the assert conditions. void function(const char *pmsg) assert(pmsg);...

3. Defensive Checks and Tracing Similar to asserts, use of defensive checks can many times save the system from a crash even when an invalid condition is detected. The main difference here is that unlike asserts, defensive checks remain in the shipped system. Tracing and maintaining event history can also be very useful in debugging crashes in the early phase of development. However tracing of limited use in debugging systems when the system has been shipped. Conclusion This paper has focused on understanding the most difficult aspect: memory management. C++ memory management is an enormously useful tool for creating elegant software. The common memory-related errors discussed in this paper can be used as a handy reference, to find and avoid such errors in your own code.