C++ Dynamic Memory

DON'T post new tutorials here! Please use the "Pending Submissions" board so the staff can review them first.
Post Reply
User avatar
Gogeta70
^_^
^_^
Posts: 3275
Joined: 25 Jun 2005, 16:00
18

C++ Dynamic Memory

Post by Gogeta70 »

Contents

1. Introduction
2. The Basics: new/delete operators
3. Pointers
3.1 How Pointers Work
3.2 Pointer arithmetic
4. Memory leaks

1. Introduction
Hey dudes. I haven't seen a new coding tutorial for a while so i thought i would type one up. I'll be covering a lot of information about dynamic memory and how it works in this tutorial, so it's recommended you have a good understanding of the basics of c++ before reading this. With that said, let's get started...

2. The Basics: new/delete operators

The new operator allows you allocate a variable size of memory that is calculated during runtime. For example:

Code: Select all

int main(int argc, char* argv[])
{
  int size = argc*73+42*5;
  
  char* buffer = new char[size]; // This will compile
  char buffer[size] = {0}; // This won't compile
}
As you can see, the 'new' operator allows you to allocate an amount of memory that isn't known before the program is compiled or run. I'll talk about the difference between using new and something like 'char buffer[size]' more later.

In addition to allocating dynamic arrays of simple datatypes, the 'new' operator is also used to create new class objects, like so:

Code: Select all


class WHATEVER
{
char *someptr;

public:

WHATEVER(void* data)
{
someptr = static_cast<char*>(data);
} // a constructor that does nothing interesting

~WHATEVER()
{

} // Empty destructor... whatever...
};

int main(int argc, char* argv[])
{
int val = 12345;
WHATEVER* object = new WHATEVER(static_cast<void*>(&val));

}

There are two different uses for the 'delete' operator that depends on how 'new' was used. If new was used to create a new class object, you would do something like the following. Let's continue with the last example:

Code: Select all


class WHATEVER
{
char *someptr;

public:

WHATEVER(void* data)
{
someptr = static_cast<char*>(data);
} // a constructor that does nothing interesting

~WHATEVER()
{

} // Empty destructor... whatever...
};

int main(int argc, char* argv[])
{
int val = 12345;
WHATEVER* object = new WHATEVER(static_cast<void*>(&val));

// We'll pretend that the WHATEVER object is useful and that we're doing some stuff with it here. Once we're done...

delete object; // Free the memory allocated for the WHATEVER object.

}

Simple as that. It's very important to use delete after every call to new to prevent memory leaks, which i will cover in more detail later.

The other method of using delete is pretty straight forward as well. This is the one i use most, since i often dynamically create arrays of data.

Code: Select all


int main(int argc, char* argv[])
{
int* numbers = new int[512]; // Allocating 2kb of memory (1 int = 4 bytes)

//Do some operations on the data here, etc etc. Once finished, free the memory:
delete[] numbers;

}

Notice the '[]' after 'delete'. You don't need to put anything in between the brackets, they're just there to let the program know you're deleting an array instead of a single object. If you used the 'delete' without the brackets, it will only free the memory for the first element in the array, causing a memory leak. More on this later.

3. Pointers

Here we are, pointers! For some reason, many new programmers find pointers very confusing. I can't lie, i did too. Pointers are actually very useful and practically required for any large scale programs. In the following section, i'll cover the basics of pointers and how they're used.

3.1 How Pointers Work

What are pointers? Simply put, they're the memory addresses that 'point' to the actual data contained in a variable. I think this is better explained with some code:

Code: Select all

int main(int argc, char* argv[])
{
int* ptr; // * is also the operator used to declare a pointer of whatever type (in this case, int) Because this operator is used for more than one thing, it's considered to be "overloaded"
int value = 54321;

ptr = &value; // The '&' symbol is also overloaded. In addition to doing the mathematical AND operation, it also is used to access the memory address of a variable. 

std::cout << *ptr; // The '*' is also used to dereference pointers. That is, it refers to the data stored at the address in the pointer.

}
If the above code is still confusing, i'll try to explain the above code more. The variable 'ptr' is created but not initialized to a value, so it contains a random address. You shouldn't dereference a pointer that hasn't been set. Let's say 'ptr' is at the address 0x7000. The next step is to create the integer, 'value' and initialize it to value 54321. Let's say that the memory address of 'value' is 0x5000.

The following line of code is this: "ptr = &value;" This assigned 'ptr' the memory address of 'value', so 'ptr' is changed from 0x7000 to 0x5000.

The last line of code is "std::cout << *ptr;" This outputs the data stored at the memory address of pointer (because * used in this way dereferences a pointer), so the output from "cout" will be '54321'.

If you still don't understand pointers (i know they can be hard to understand), go to http://cplusplus.com/" onclick="window.open(this.href);return false;" onclick="window.open(this.href);return false; - They have a very good tutorial on pointers.

3.2 Pointer Arithmetic

Once you understand pointers, you can then directly manipulate them using the math operators. Mainly + and -, but you can divide and multiply them too, if desired.

Why would this be useful? Let's say you have received a a bunch of random data. You don't know how large the data is in memory, so the first 4 bytes of the data tell you the size of the entire buffer. An easy way to do this is pointer arithmetic:

Code: Select all


int main(int argc, char* argv[])
{
int size, *gsize;
char* buffer;

gsize = static_cast<int*>(get_some_data()); // Some function that returns data from wherever

size = gsize[0]; // Get the first 4 bytes of the data (this is an integer array, so each element references 4 bytes)

buffer = static_cast<char*>(gsize); // Convert the 'gsize' pointer to a char pointer (char has a size of 1 byte)

size -= 4; // Subtract 4 from the 'size' variable because we don't want to include the first 4 bytes of the data in the loop.

buffer += 4; // Increment the pointer by 4 to skip the 4 bytes that contain the size of the buffer.

for(int i = 0; i < size; i++)
{
// Do some processing on the data here
}

// We didnt' call 'new' to allocate the pointer, so we don't need to 'delete' it.
}

In that example, we increased the address of the pointer by 4 to prevent reading the first four bytes of data. This achieves the same effect as doing the loop with "int i = 4; i < size; i++" without decreasing 'size' by 4, but i think manipulating the pointer directly is cleaner. It's a matter of opinion, i guess. But now you know how to do pointer arithmetic.

4. Memory leaks

Memory leaks... what are they? How do they happen? Well, they can be tricky. A memory leak occurs when you allocate memory with 'new' without calling 'delete' on the data when you're done with it. The 'new' operator allocates memory on the heap, otherwise known as ram, and it stays there until free. If not freed, it will remain in use and will not be available to other programs - even when your program ends.

Yes, you heard me right. If you allocate 1gig of memory with new, don't free it, and close your program - You're down 1 gig of ram until you restart your computer.

So the basic idea is, for every call to 'new' you want to also call 'delete' or 'delete[]'. Another major problem that is very hard to debug is when you write too much data to memory allocated with 'new'. It won't crash your program there, but rather on your next call to 'new' or 'delete' - sometimes. Sometimes it'll take several calls. What happens is when you write 2kb to a 1kb buffer allocated by 'new' you fuck up the internal structure used by the 'new' and 'delete' operators, which really jacks things up. I don't know specifics - but it does happen. So don't do anything like this:

Code: Select all


char* buffer = new char[1024];

for(int i = 0; i < 2048; i++)
{
buffer[i] = i >> 24; // doing a bit shift right so that the loop never writes a value higher than 255 to buffer[i], which is a 'char' type (1 byte!).
}

delete[] buffer;
Just make sure you never write to or read from memory that isn't allocated before hand, and always check that you're writing within the bounds of your arrays.


Earlier in the tutorial, i said i would cover the difference between using 'new' and just statically defining the size of your buffers. Well, here goes.

When in local scope, that is, when operating inside of a function, any variable defined will be stored on the stack. The stack is different than the heap - it's faster but has a lot less memory. On windows, each thread in a program is given a default of 1mb for the stack. The heap, on the other hand is all of your available ram + page file (on windows). On linux, i guess it would be your available ram + swap (swap is a partition used by linux operating systems).

Since the size of the stack is relatively small, trying to allocate large amounts of memory on the stack could cause a stack overflow, resulting in your program crashing. So, on windows this would cause a stack overflow (1mb stack):

Code: Select all


void myfunction()
{
char[1024*1024+1] = {0}; // ouch, i allocated 1 byte over 1mb
}

However, if you do the same as above with the new operator, you leave your stack available for smaller variables and your program doesn't crash.


Anyway, that's all for this tutorial. I wrote half of it without being sober, so sorry if it's shitty, lol.
¯\_(ツ)_/¯ It works on my machine...

User avatar
lilrofl
Siliconoclast
Siliconoclast
Posts: 1363
Joined: 28 Jan 2009, 17:00
15
Location: California, USA
Contact:

Re: C++ Dynamic Memory

Post by lilrofl »

I'm not a real coder, so it was nice to read a coding tutorial and understand it as well. Thanks for writing this up buddy =D>
knuffeltjes voor mijn knuffel
[img]http://i911.photobucket.com/albums/ac320/stuphsack/Sig.jpg[/img]

User avatar
Gogeta70
^_^
^_^
Posts: 3275
Joined: 25 Jun 2005, 16:00
18

Re: C++ Dynamic Memory

Post by Gogeta70 »

I'm glad you liked it.
¯\_(ツ)_/¯ It works on my machine...

User avatar
Lundis
Distorter of Reality
Distorter of Reality
Posts: 543
Joined: 22 Aug 2008, 16:00
15
Location: Deadlock of Awesome
Contact:

Re: C++ Dynamic Memory

Post by Lundis »

Looking good, just one small detail:

Code: Select all

char* buffer = new char[1024];

for(int i = 0; i < 2048; i++)
{
    buffer[i] = i >> 24; // this makes any value below 0xFFFFFF zero, as you're shifting out the 24 least significant bits.
    buffer[i] = i & 0xFF; // this discards the higher bits, leaving only the 8 least significant ones [0,255] (logical AND )
}

delete[] buffer;
But you don't actually have to do that at all, the compiler will implicitly "convert"* the number to a byte because buffer is a byte array. This also works without corrupting anything, and it'll only move the 8 least significant bytes:

Code: Select all

char* buffer = new char[512];

for (int i = 0; i < 512; i++) {
	buffer[i] = i;
	if ( (i % 8) == 0) // only print every 8 letter to limit the output
		std::cout << (int)buffer[i] << " ";
}

delete[] buffer;
What happens when you write to memory addresses beyond what you've allocated space for is:

1) if your program has access to the memory:
You'll corrupt other data that your program has allocated, which often shows up as seemingly random crashes later on.

2) if the memory is owned by another process (or isn't owned by any process, i.e. free memory)
The OS will notify your program that it has accessed illegal memory, on linux this leads to a segfault.

* It simply uses a machine instruction that only moves one byte (least significant bits) from the register i is in to the memory location.

I'm moving this to the tutorial section.

Post Reply