Hello, and welcome. Now that we understand dynamic memory allocation,
we have to implement it, we have to actually know how to do it in c code.
So we're going to talk about three really important functions, malloc,
calloc, realloc.
You see that they sort of have names where memory allocation
calloc and reallocation.
The simplest and most common way to allocate memory
is through the standard library function named malloc.
This is allocating dynamic memory at runtime.
In order to call this function, you need to include the standard lib header file.
You specify the number of bytes of memory that you want allocated as its argument.
What's returned from this function is the address of the first byte of memory that is allocated.
Because you get an address returned, a pointer is the only place to place it put it.
So this is why dynamic memory allocation is only possible
via this concept of a pointer because it returns addresses.
You can only put an address in a pointer, that's what a pointer stores, a pointer stores an address.
So here's how you do it very, simple syntax.
Usually, when create your pointer, you don't have to.
You could create your pointer and set it to null.
And then in the next line, you could malloc.
But here is the standard syntax you're creating your int pointer,
pointer to a type int.
And then you'll notice that you call malloc.
And in here, we're saying malloc, we're going to we're going to create 100 bytes
for malloc and then we're going to cast it to an int pointer.
Because malloc only returns a address,
you have to cast it to an end pointer
because that address is storing an int pointer.
This is the standard way to do it.
Now you're requesting 100 bytes of memory
and assigning it to the address of the memory block to p number.
pNumber is going to point to the first int location at the beginning of the 100 bytes that were allocated.
So what this means is since integers are 4 bytes a piece,
it can hold 25 int values.
And again, this is assuming that you have 4 bytes.
Now depending on the system that you're running, they may be larger, maybe smaller.
So you should see the problem with this. This code's not going to work
on different systems because you're allocating based on an assumption,
based on the assumption of the int size.
So it's not really good to use malloc in this manner
with a constant number 100.
What you want to do is you want to use the sizeof operator.
So it would be better to remove the assumption that ints are 4 bytes.
Instead if you want to allocate room for 25 integers
inside the malloc argument,
specify the number of integers you want to allocate and then multiply that by the size of the integers.
And this will make the code much more portable.
This code will now work on different systems, different operating systems, different architectures.
You still cast the return value the address because it has to be pointing to a type int,
so you say int star. The only difference here is you use the sizeof operator.
Sizeof operator is really important when you're allocating memory using malloc
or using calloc or realloc.
You always use and want to use the sizeof operator
for the type that you're allocating and then multiply that by how many
particular numbers you want,
so if you want to store 25 int, so you do 25.
So the argument of malloc above is clearly indicating that sufficient bytes for accommodating
25 values of type int should be made available.
Also, as I mentioned, notice the cast. This converts the address,
returned by the function to the type pointer to int and int pointer.
Malloc returns a pointer of type void, that's what it makes it so useful.
It returns that address but it's a it's a void pointer.
Remember, we talked about void pointers and why void pointers are useful because
now you can have a function that can take different parameters
and it can return a void pointer. Now you're seeing the power
of returning a void pointer,
again, but to use it, to d to actually assign data to it, to de-reference it,
you have to cast it.
If we didn't want to cast this, we could say void star p number.
We don't cast it, but then we would have to eventually cast it
once we wanted to de-reference it.
But that's why we have to cast from malloc. It's returning an address and you want it to point to
an int type, and it's returning a void type.
You can request any number of bytes.
However, it is dependent on your system and how much memory you have on your system.
So if you try to create or try to request a ton of bytes
or there's some limitation, it could fail.
And when it fails, it returns a value of null, malloc will return null.
And this is good because pointers when pointers are pointing null,
it means that they're not pointing to any memory,
so you can't corrupt or change any memory inadvertently.
So it's always a good idea after you do a malloc
to immediately check for null before you use it,
just like it is when you assign data
or just like it is when you de-reference a pointer.
You want to make sure that you're actually pointing to something.
So here, we're allocating some memory for p number our pointer
and then we're saying if not p number.
Remember, the null is a zero.
So you can just use a not on it. You don't have to actually compare it to null.
You can, you can compare it to null it makes it more readable, maybe makes it more explicit.
But you can just say not p number, and that's actually saying not one,
and null is zero. So this will check for null.
And if it is null, that basically means that memory was not able to be
allocated. If you're not able to allocate memory in your program, you're in trouble.
And so what you should usually do in this code to deal with a memory allocation failure
is you should abort, you should call exit, you should
get out of your program. Because if your program can't allocate memory, your program's hosed.
So usually this is you call an accident abort if you can't allocate memory.
At least when you do this, you can display a message
terminate the program. It's much better
than allowing the program to continue when you weren't we're not able to allocate memory.
Because then you may continue you may try to use that memory,
it's going to be null, so the program is going to crash anyways
but you're not necessarily going to know why.
You have to look at the call stack. It will take a lot longer to debug that error.
Releasing memory. Now I mentioned that since you're
the programmer is responsible for allocating the memory,
you have to get rid of the memory when you're no longer using because memory is a vital resource,
especially on embedded systems.
You shouldn't be using memory if you don't need it. So you have to release the memory.
You always want to release the memory when it's no longer required.
Memory that you allocate on the heap will be automatically released when your program exists,
which is good because it does do some cleanup.
But it's much better to explicitly release the memory when you're done with it
even if it's just before you exit.
It's cleaner,
always do it free or always release the memory after you allocate it,
you want to follow this rule. This is a stringent strict rule.
Always release memory if you allocate it.
A memory leak occurs when you allocate some memory dynamically
and you do not retain the reference so you're unable to release it.
Maybe you're out of scope for that reference you can't. You don't release it.
That's called a memory leak. There's memory allocated but it's no longer being used.
This often occurs when you're in a loop, and you're allocating memory in a loop.
You're in the loop. You allocate this memory. You use it inside the loop,
and then you jump out of the loop and you no longer release it.
Each loop iteration is allocating it.
So to free memory that you have allocated dynamically,
you must still have access to that address.
So usually you want a free memory when you're done using it and
usually in the method that you allocated it.
However, if you are passing that memory to another function and then
it's that function's responsibility, but usually you want to release the memory
and the function that you allocate it.
To release the memory for a block of dynamically
allocated memory whose address you have stored in a pointer,
all you have to do is call the free function.
This is in the standard lib as well. You just pass in the pointer to the free function.
And usually, after you pass that pointer, you want to set it to null
because that's saying that you have no memory allocated for it.
The free function has a formal parameter of type void star,
so you can pass in any pointer type
and it will work.
You don't have to actually cast it or anything like that.
As long as p number contains the address that was returned when the memory is allocated,
the entire block of memory will be free for further use. Other programs can use that memory,
your program can now use that memory,
significant when you're on an embedded system.
And then as I mentioned, you always want to set the pointer to null
to signify that that pointer is no longer pointing to anything.
That memory has been freed.
There's another nice function called calloc.
This has a couple advantages over malloc. It's very similar to malloc,
but what it does is it allocates the memory as a number of elements in a given size
and it also initializes the memory.
That's the difference, initialization. I talked about the importance of initialization.
That's why you always want to
initialize data so that you you understand that
there's data in there that you're expecting.
When you uninitialize data, there could be data in there that you don't have any clue about.
So always initialize your data with something.
And calloc is a little bit better because it does it for you.
It takes two arguments, has two parameters.
The number of data items for which space is required, the size of each data item.
It's declared in the standard lib header.
You call it just like you call malloc. Looks pretty much the same way.
In this example, we're creating an integer pointer,
and we're allocating 75 integers of
sizeof int. So it takes two parameters.
It's a little cleaner because you don't have to actually do a multiplication.
In malloc, you had to do 75 time size event.
Here you just say 75,
you're creating 75 ints and the size for each is
4 bytes or whatever the system returns from the size of operator.
But again the significance of this is it's going to initialize all that data as well,
the return value could be null if it's not able to allocate that memory which in turn you shouldn't abort.
But it's very similar to using malloc except the big plus is this initialization.
All the ints are initialized to zero, and that's a really big deal,
big advantage.
It's also a little bit easier to use with the two parameters.
Let's talk about another function called realloc.
The realloc function enables you to reuse or extend memory that you previously
allocated using malloc or calloc.
So this is a way to dynamically change how much memory is allocated
while you're running.
So you could initially create so much memory and then you realize that,
that's not enough memory. Well, what you can do there is you can just call realloc,
and this will reallocate memory.
It expects two argument values, a pointer containing an address that was
previously turned by a call to malloc.
So you have a pointer that has some memory allocated or calloc
and then the size and bytes of the new memory that you want to allocate it.
So this will allocate the amount of memory you specify by the second argument,
that's the second argument. It transfers the contents of the previously allocated memory,
referenced by the pointer, so it automatically knows how much memory you had
previously allocated because you pass in the pointer.
And then it will actually add to that, the newly allocated memory.
And it returns a void pointer to the new memory.
So you have to recast it when you call realloc just like the other ones.
It returns null if you weren't able to realloc,
which again is probably a fatal error and you should abort.
The most important feature of this operation is that realloc
preserves the contents of the original memory area, you're just extending that memory.
It may be fragmented because you may not have been able to do it in sequence,
but you still have all that memory that you can assign to now data.
Let's look at an example.
In this example, we're going to use malloc and then we're going to realloc the memory
and then we're going to free it.
So here we're creating a character pointer.
We're not initializing it to null, we probably should be.
But it doesn't have any memory allocated to for what it's pointing to.
It has memory allocated to store the pointer,
which is just an address. But if you try to go to that address, your program will be host.
So what you do is you call malloc.
Here we're saying malloc 15.
This will create 15 bytes for a character pointer.
We cast it, because it returns a void pointer. So str
is a pointer now that has memory allocated for what it's pointing to.
We then can use str copy to pass in some data to
what it's pointing to, a string
and we print out the string in the address.
Well, then we realize that what we allocated 15 bytes
is not big enough.
So then we call realloc,
and we pass in the pointer which is str and we want to realloc 25 more bytes
and then that returns a void point as we have to cast it again.
Now we're still going to have in memory Jason.
So now we can call string concatenated, and we concatenate to the string com,
so now it'll be Jason.com and then we print it out.
And then last, but not least, probably the most important thing of this
of this slide, so we don't have a memory leak is we call free.
Always call free when you call malloc.
So let's take a look at this example inside code blocks.
Here's our code example inside
our code blocks editor.
And the first thing you should notice is we're
including the standard lib header file because that's where our malloc
functions and our realloc functions are in as well as our free function.
Also because we're using some str functions,
concatenation and copy, we're including string.h.
Now on line eight, we're going to create our character pointer.
It has no memory allocated for it.
We should initialize this to null
because it's not pointing to anything yet. On line 11, we call malloc.
And we should probably say 15
times the size of char
in case the size of bytes of char is different
and then we cast it
into a character pointer because malloc returns a void. We then copy it.
If we tried to copy this and we didn't do anything on line 11,
the program would crash because we have no memory allocated for what string points to.
We then just print out the string and the address.
Now we call realloc.
We need to store more data than 15 bytes,
so we're going to realloc with 25 bytes
and we probably want to do again sizeof
char to be consistent.
We cast this and then we concatenate
onto our existing memory location that contains jason.com, and we're going to print out jason.com.
And then last but not least, on line 20, we free that memory up
which is really important.
So let's go ahead and run this. And what you'll just see is that
when we print it out the second time, we have the concatenation.
So our first string,
we have memory allocated and it's an address 000C2918.
Then we created a new address by reallocating and taking the existing size of the pointer
that we passed it as an argument.
So 000C0CD8
is actually has more data allocated to it,
the existing data in jason and 25 more bytes.
And so what you notice is the concatenation works.
Because when we reallocated,
we put the contents jason in that memory and we just extended it.
So a lot of advantages to using these malloc, these calloc and free realloc calls.
You want to use these, you want to understand these. Pretty simple functions to use.
So in our last slide, let's just go over some guidelines.
You want to avoid allocating lots of small amounts of memory, it's not efficient.
When you allocate memory on the heap,
it does have some overhead with it.
And if you allocate small blocks of memory, it's going to have much more overhead than allocating
fewer larger blocks.
So allocate the memory that you need,
but don't do it every two statements with reallocation,
do large chunks.
Only hang on to the memory as long as you need it. Memory is a scarce resource.
When it's on the heap, it's even more scarce.
So when you're finished with a block of memory, call free.
Always ensure that you provide for releasing memory that you've allocated,
decide where in your code you will release the memory when you write the code that allocates it.
Make sure you have access to that pointer.
If you need to make the pointer global, then you need to do that.
But usually wherever you allocate in that function, you should release it in that function.
If you have other functions releasing memory for you,
you're then assuming that not good. There's too much coupling and dependencies there.
The person that creates it should be the person that deletes it.
Don't put the responsibility on to other functions and such.
Make sure you do not inadvertently overwrite the address of memory you've allocated on the heap
before you have released it, this will cause a memory leak.
So if you try to write too many bytes to it or if you have overflows,
you may have issues.
So be especially careful when you're allocating memory within a loop
because this can cause problems in buffer overflows.
I hope this was helpful. Dynamic memory allocation a big, big deal.
I'll give you a challenge that helps you become more familiar with this,
but make sure you understand this concept. Thank you.