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
}
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));
}
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.
}
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;
}
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.
}
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.
}
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;
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
}
Anyway, that's all for this tutorial. I wrote half of it without being sober, so sorry if it's shitty, lol.