Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

52 Avoiding Memory

Overwrites

Technique

Save Time By

Understanding memory overwrites

Preventing memory overwrites in arrays and allocated blocks

Protecting your data

Interpreting the output

Memory overwrites are a bane of the C++ programmer. A memory overwrite occurs when you write to an area outside an allocated block. This is bad, since you are writing to a block of memory

that you may or may not own, but certainly did not intend to modify. For example, if we have a C++ statement that says

char line[10];

and we then write something that says

line[11] = ‘a’;

we have overwritten a valid part of memory and might cause problems in the application later on. Unfortunately, memory overwrites like this are somewhat hard to track down without specialized tools and software. Alternatively, of course, you can simply avoid writing outside the valid bounds of an array or allocated block, but that is a bit easier said than done. The real problem here is that when we write to position 11 of the ten-block array, we have overwritten something in memory. That something could be another variable, it could be the return address for a function, or it could be a pointer that was previously allocated. None of these are good things. You need to stay within the memory allotment that you have requested for a memory block.

This technique looks at a way to use the C++ coding concepts to protect the data we are working with from being overwritten. This is a basic concept of encapsulation in C++: If you have data, you need to make sure that the data is used properly. That means not assigning values outside a valid range; it also means not overwriting bounds that have been established.

Creating a Memory Safe Buffer Class

The most common memory overwrite case occurs when strings are copied and assigned values. This error, which typically shows up with the use of the C functions strcpy and memcpy, occurs because the functions do not “know” how big a string is, so they cannot protect against

308 Technique 52: Avoiding Memory Overwrites

the string boundaries being overwritten. In order to fix this problem, we will create a class that understands its own boundaries and only allows the correct number of characters to be written to or copied into the object. In this way, we will create a generic buffer class that can be safely used to store strings in all of your applications, saving you time in implementing the class and in debugging memory overwrites. Here’s how:

1.

2.

In the code editor of your choice, create a new file to hold the code for the technique.

In this example, the file is named ch52.cpp, although you can use whatever you choose. This file will contain the source code for our classes.

Type the code from Listing 52-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 52-1: THE BUFFER CLASS

#include <iostream> #include <fstream>

using namespace std;

class Buffer

{

private:

char *_buffer; long _length;

virtual void Init()

{

_buffer = NULL; _length = 0;

}

virtual void Clear()

{

if ( _buffer )

delete [] _buffer; Init();

}

public:

Buffer(void)

{

Init();

}

Buffer( const char *buffer, int length )

{

Init();

SetBuffer( buffer, length );

}

Buffer ( int length )

{

_buffer = new char[length]; _length = length;

set( 0 );

}

Buffer( const Buffer& aCopy )

Creating a Memory Safe Buffer Class

309

{

Init();

SetBuffer( aCopy._buffer, aCopy._length );

}

virtual ~Buffer()

{

if ( _buffer )

delete [] _buffer;

}

Buffer operator=(const Buffer& aCopy )

{

Clear();

SetBuffer( aCopy._buffer, aCopy._length ); return *this;

}

Buffer operator=(const char *buffer )

{

Clear();

SetBuffer( buffer, strlen(buffer) ); return *this;

}

char& operator[]( int index )

{

if ( index < 0 || index >= _length )

 

4

throw

“Buffer: Index out of range”;

 

return _buffer[ index ];

 

 

}

 

 

 

 

Buffer operator()(int st, int end)

 

 

 

{

 

 

 

 

// Validate the pieces.

 

 

 

if ( st <

0 || st >= _length )

 

 

 

throw

“Buffer: Start index

out of range”;

 

 

if ( end < 0 || end >= _length

)

 

 

throw

“Buffer: End index out of range”;

 

 

Buffer b( _buffer+st, end-st+1

);

 

 

return b;

 

 

 

 

}

 

 

 

 

void set( char c )

{

for ( int i=0; i<_length; ++i ) _buffer[i] = c;

}

virtual void SetBuffer( const char *buffer, int length )

{

_buffer = new char[ length ]; for ( int i=0; i<length; ++i )

_buffer[i] = buffer[i]; _length = length;

}

void empty()

(continued)

310 Technique 52: Avoiding Memory Overwrites

LISTING 52-1 (continued)

{

set( 0 );

}

int Length() const

{

return _length;

}

};

ostream& operator <<( ostream& out, Buffer &b )

{

for ( int i=0; i<b.Length(); ++i ) out << b[i];

return out;

}

void func1()

{

char *buffer = new char[10];

strcpy( buffer, “This is a really long string”); cout << “Func1: [1]” << buffer << endl;

memset( buffer, 0, 11 );

cout << “Func1: [2]” << buffer << endl; strcpy( buffer, “This is a short string”); buffer[ 12] = 0;

cout << “Func1: [3]” << buffer << endl;

}

void func2()

{

Buffer b(10); try

{

b = “This is a really long string”; cout << “Func2: [1]” << b << endl; b.set( 0 );

cout << “Func2: [2]” << b << endl; b[12] = 0;

cout << “Func2: [3]” << b << endl;

}

catch ( ... )

{

printf(“Exception caught\n”);

}

}

int main()

{

func1();

func2();

}

1

2

3

Creating a Memory Safe Buffer Class

311

The two functions shown above illustrate the memory overwrite problem, first as addressed by the standard C++ allocation of arrays (func1, shown at 1) and then using our Buffer class to handle the problem (func2, shown at 2). In each case, we allocate a character buffer of ten characters. In the func1 case, we then copy a string that is much longer than ten characters into the buffer. This causes a memory overwrite and could easily crash your program. However, because standard C++ has no way of detecting this, you will not see the problem immediately. In the second case, func2, we are using our Buffer class, which detects the problem as soon as you try to copy the larger string into the small allocated buffer and report it.

3. Save the source code in your code editor and close the editor application.

4. Compile the application, using your favorite compiler on your favorite operating system.

5. Run the application in the console window.

If you have done everything right, you should see something similar to this output from the application:

$ ./a.exe

Func1: [1]This is a re1 Func1: [2]

Func1: [3]This is a sh

Func2: [1]This is a really long string Func2: [2]

Func2: [3]

Note that you may see different output on different operating systems, depending on how the error occurs and whether or not it is visible. For func1, we see that the string does not get set to what we expect it to. You might see the string “This is a really long string” which would be even worse. In the func2 case, however, we never assign the strings that are too long, because an exception is thrown and the code properly handles it.

As you can see, in the “pure” C-style access code (shown at 1), there are no checks to see whether the programmer overwrites the buffer that is allocated. Obviously, in your own code it would not be quite as straightforward to find the problem. There do not appear to be any problems with the code here; everything prints out and continues processing properly. However, damage has been done to structures in memory — and these might or might not show up as program crashes. Worse, you might give the user incorrect output upon which they may make invalid decisions.

In the second case, C++ style (shown at 2), you can

see that the code protects against

overwrites and

 

 

 

 

 

throws an exception (shown at the line marked

 

4

and caught at the line marked

 

3

invalid

 

 

) when an

 

 

programmer to imme-

write occurs. This allows the

 

 

 

 

 

diately find where the problem happened and to fix it quickly and easily.