Welcome to this course about pointers in C. This introductory course was expertly created by Jalal, perfect for absolute beginners. This visual and code rich course will help you understand why pointers are crucial in manipulating data values. Pointers are variables that store the memory address of another variable. They point to the location of data in memory. With a bunch of examples, this course demystifies pointers and their various uses, covering topics such as passing by reference versus value, void pointers, arrays, and more. So buckle up and get ready to demystify the world of pointers in C. Hello,
my friend. Welcome to Free Code Camp, the best place in the internet where you can learn to code for free. Fantastic, mesmerizing, beautiful. Today, I'm going to talk to you about pointers. Yeah, another course on pointers. Why is that? Well, because I want to add something. I want to tell my point related to this very tricky idea, to this very tricky concept. I will try to do my best, basically disease the video I used to need when I started to learn how to code. So this is like an explain, like I'm five explain, like I'm
a beginner, pointers. So this is going to be very visual and very code based. I'm going to do a lot of examples, and hopefully you are going to tame this complex principle and start to the reference all the way down. This is my goal. To do that, we have to build up from first principles. So we're going to start from the very, very beginning. My friend, when I say the very beginning, I'm saying, what is a computer? We start from there. So to explain really what is a computer, you need a course on computer architectures.
We don't have the time and we don't want to do that. So we need a trick. We need a way to explain a very difficult concept in few words. Well, abstraction, right? Obstruction is the key to collapse difficult, complex entities into manageable ideas, right? We want to collapse complexity into intelligible models. That's what we are going to do now. I'm going to try to explain to you what is a computer in a nutshell. So let's pretend an alien is coming to the planet and is asking to yourself, hey, how does your body work? What is
going on there? Well, it's kind of a tricky question, right? Because there is a lot that is going on. You can start to explain to the alien, well, you know, we are made up of atoms, especially we are made up of carbon. We can consider ourselves like carbon machines, some hydrogen, some oxygen, some nitrogen, and yeah, kind of complex, right? If you want to describe yourself in a Rick and Morty fashion, like saying every atom, what is doing it? Well, I guess you're lost. A better approach, I would argue, is to tell the alien, well,
you know, I have some systems inside myself that are providing some functionality. For example, I have the nervous system, which is composed of some organs. These organs cooperate together to process my reality, right? Something like that. In the same vibe, you can say, well, I have a digestive system that is processing food that I take from nature. And this food is converted to energy, energy that I can use for my nervous system to process reality, but I don't know, as well for my muscles. So I have fuel to move around this body. You got the
point, right? You explain reality at a specific level. You don't want to explain from an atom level, right? It's too low. You cannot explain as well from a cellular standpoint, right? Still too low. You talk on a level of systems, right? You simply say, I have the system that is providing this function. This other system that is providing this function, altogether, these systems provide me. That's what is going on. Well, in the same vibe, I want to explain to you what is a computer. At the end of the day, a computer is just a super
fancy calculator, if you want, especially when I code, when I write my C programs, I perceive the computer just a simple, arithmetical, logical calculator and a place in which I can stock all my numbers. So I see the RAM, the memory of the computer, as a big chunk of boxes with a specific ID, with a specific address. You will see in this video, this is the gist of it, addresses indeed, pointers. On the other end, I see a calculator, a calculator which is an arithmetical, logical calculator that is able to perform, indeed, arithmetical and logical
operations. Logical operations entails something like, is this number equal to this one? Is this number less than this one? And so forth. Very simple stuff. So that's basically it. That's how I perceive my computer, an arithmetical, logical calculator, and a place in which I can stock numbers that will be processed by this arithmetical, logical calculator. Of course, in the real hardware, I'm talking about the CPU, which is the central processing unit. You can see in the image that the CPU as inside the ALU, which is the arithmetical, logical calculator, I was talking to you about,
arithmetical, logical unit. Inside the CPU, we have also the registers, which are intermediate places in which we perform the operations in intermediate places in which we stock numbers. And then we have the giant RAM, giant, quote-unquote giant, because it can be 4 gigabytes, 8 gigabytes, 16 gigabytes, and so forth. Okay, so let's dive into the RAM a little bit more. I told you that I perceive the RAM as a series of post boxes, essentially, with a specific address. Every post box is a byte, okay? You know this stuff very well. And every byte is just
a line of 8 light bulbs. As you can see, every byte has a specific tag, which is the address of the byte. So I have the physical view of the RAM, which is a chip, and the logical view of the RAM, which is my programmer perceived view of what this chip is offering me, right? A series of bytes, as easy as that, okay? So we have this view of what is the memory. So inside this memory, I told you that there are these boxes. I told you that these are bytes. And what is a byte?
A byte is 8 light bulbs. Of course, this is an obstruction for a transistor, but we can perceive like a light bulb that can be turned on and off. A byte is indeed a byte, a chunk of information. I can stock a specific pattern inside this byte, okay? Given that I have 8 bits, I can have 256 possible configurations. So every configuration is gonna mean a specific number from 0 to 255. Of course, in a binary way, in a binary base, because we are dealing with a binary machine. Of course, these light bulbs can be
turned on and off in a binary fashion. The name byte is just a kind of joke, because 4 bits are enabled. So everywhere in computer science, there is plenty of these kinds of jokes. You will find out. And as you can see here, I have the address of the byte, which is a number. You see this strange number with symbols, but this is an hexadecimal notation. This is an hexadecimal number, but keep in mind that this is just another way of representing a value. I can write the number 42 in hexadecimal, in binary, in octal.
This is just a different way of representing the same thing. You can make a parallel. For example, if you say i, privet, ciao, all these things mean the same thing. They mean i, but they are in different languages. So the value is the same, only the symbols are changing. So it's just a number. That tag is just a number, which is the address, the location in memory of this byte. So inside these bytes, I can put stuff. I can put numbers. To be more technical, I can put data types. You can see I have these
data types. I have char, short, int, long, float, double. To be more pedantic, short and long are just qualifiers. They are short int and long int, but we can consider them like data types, per se. When I declare a variable, I just use short and long. I never write short int and long int, but this is an aside. This is not important. From this slide, you have to understand a few key ideas. So the data type is of a specific size, as you can clearly see from the image. You have a char, which is one
byte, and you have a long, which is eight bytes, generally speaking, depending on the machine. For example, in my macOS, I have a long, which is eight bytes. Generally speaking, this is true even for you, I guess. So the data type is going to discipline the size that we're going to ask to our compiler in the RAM. And of course, the data type is going to discipline the way in which bits are going to be arranged. So we have two families, basically. We have the integer type and the real type. The way we stock data
is qualitatively different. In it, when we stock float, we have a different pattern configuration. When we stock real numbers, we have one bit for the sign, which is equal for both. And then we have some bits related to the exponent. So we have a qualitatively different way of stocking these numbers in memory. You will see later. We're going to dig a little bit about this idea. So key idea, integer types and real types have a different way of encoding in memory, binary speaking. So the bits are going to be arranged differently. You will see with
a clear example later. And depending on the specific data type, you're going to take much or less memory. If I ask for a long, I'm asking for a big chunk of memory, eight bytes. If I ask for a char, I just need one byte. Indeed, in a char, typically we stock chars. But at the end of the day, a char is just one byte integer. It is an integer indeed. Okay, so we have a RAM. We can stock data inside this RAM that can be in the real and integer form, depending on how big these
numbers are going to be. I'm going to take less or more bytes. So as you can see, I can write numbers inside my RAM. To me, it's just a piece of paper in which I can write numbers, numbers that I will process with my algorithm. Okay, that's all there is. I see the RAM like a piece of paper in which I can write stuff. Of course, this stuff is going to be a number. Of course, this number is going to be in a binary form because we have light bulbs that we can manipulate. But at
the end of the day, with these numbers, I can create everything. I can create this video that you're now watching. So let's make a clearer example. The RAM electrical binary state is giving some meaning, is conveying some meaning. In this case, I've declared the variable, which is nb, and I have assigned the value 42 to this variable. As you can see, the bits are turned accordingly, right? Here I have a 101010. This is indeed the binary configuration of the number 42. I hope you know how to translate from binary to decimal. If you don't know,
just watch the video. It's very simple. So here I've written in a space in memory, which is that specific address, the value, 42. The specific configuration, the specific details by which numbers are stocked is called ndianness, if you want to search. Difference between little ndian and big ndian. But again, this is not related to our topic. Let's focus only on the number itself. Here, as you can see, we have a binary configuration that is conveying the number 42. Cool. Now let's watch the binary configuration of the number 42.0. As you can see, it's totally different.
It's totally different. Because as I told you, we have a standard that we're following. Basically, engineers gathered and decided that, okay, this is the best way to stock a float number for various reasons. And we have these eight bytes that are the exponent for floats. Now, the idea that I want to convey to you is that the bits are totally different. Keep in mind this idea, because it's going to be useful in the video where we're going to just do some pointer manipulation. So to make the idea of this introductory part, by now we haven't
talked about pointers at all. But I think it's useful to have this introduction to be everyone on the same page. So we have an electrical notepad, the place in which we stock numbers. The keyboard is my pencil, right? Is my electrical pencil. I input inside my RAM the numbers, thanks to the code I'm going to write. And the CPU is my arithmetical logical calculator. Every time I say, if A equals B, or if I say printf A per B, well, all these operations are going to happen inside the central processing unit, which is just a
place in which processing is happening. So keep in mind this view, you have a place in which you can give the input to the computer, a place in which you stock number, a place in which you process number, and a place in which you see the results. Super fancy calculator, my friend, you see? You can do this stuff analogically, right? With a calculator and a piece of paper. Or you can do with computers that are much, much better, much, much faster. And you can do, of course, algorithm with computers, so to make calculations automatic. So
this is the intro. This is the view, which I think it's fairly useful to understand pointers. And we are ready to go. Okay, okay, this is all cool. A computer is a fancy calculator and a piece of paper. But go to the point. Cut to the chase. And indeed, I will. Now we are going to talk about the main point, namely the main function. You surely know that the operating system is the master program that is taking control of all the resources inside your computer, right? It's the king, is the medium between me and the
bourbon hardware. This is a super-complicate program that is able really to control the machine at the lowest level of reality. Every time I create a program, I'm asking the operating system to give me some computer resources. Inside my programs, I have a special function, which is the main function. I call it the queen function because it's going to be the first one that is going to spark, that is going to emit all the process. A process is a fancy word for referring to a program that is running. So first of all, let's clarify what is
a function. Well, at the end of the day, a function is a black box magic. It's a piece of code that is able to perform a specific task. The best example I can think of is the printf. How many times you have used the printf function without knowing absolutely anything. You have a piece of code that has been written by someone else. You can use including the standard input output and boom, you have it in the terminal, the string hello world. You don't know a line of the implementation of printf nonetheless you use on a
daily basis. That's the power of abstraction, that's the power of functions. So to me, it's a black box. I have an API that has been given in which is written, which are the specific inputs that I have to put inside parentheses and the specific syntax. And given that, I can use the function very easily. Of course, I'm talking about black boxes referring to other people code. If you have to implement your own functions, of course, you know what is going on underneath the word, right? The thing is that with code, we generally leverage other people
code already written. So for us, the black box magic is a really good metaphor. So now let's clarify why the main function is the queen. Well, every program can be composed of many, many functions. For example, I don't know, Facebook is composed of many, many functions. For example, low world is composed of the main function and the printf, two functions. So a very simple program. Generally speaking, we can think about a process composed of many functions. So we have this situation here. We have the main function that is calling another function as well as calling
another function and so forth. Many, many functions calling one each other. Then of course, I'm going to have the return values for all the functions. And a process overall is this giant calling of functions on each other. So we have all these black magic boxes that are cooperating on each other to perform a specific task to perform a specific application, right? The main is pretty important because it's the Tinder is the one that is going to spark all this madness of functions calling one each other. Okay, pretty cool, but where is all this action? Well,
again, we have to ask the operating system for resources, especially memory. And that's exactly what is happening. When we launch a process, we ask the operating system for some memory. And that's what we're going to get. We're going to get so-called process memory layout, which is this very strange arrangement of memory. We have a stack, heap, the data, and text. So these are sections that are going to contain pieces of your process. For example, in the text, you're going to have machine instructions. In the data, you're going to have, for example, global static variables. In
the heap, you have the memory, which is going to be allocated dynamically at the runtime. And in the stack is where all the action is taking place. So to make it very simple, the operating system is controlling all the RAM, all the resources, and is saying, okay, I have this process. I need to give him a specific spot in memory because he has to perform his stuff. So he's going to say, okay, take this field in memory, do your stuff, and then give me back. I need the control back. That's what is happening. So let's
zoom in a little bit, and let's try to understand better what is going on. This, my friend, is the RAM. It's the place in which you stock your numbers. You can view the RAM as a chip, or you can view as a programmer the RAM as a logical, contiguous series of bytes. Here is where your process is going to take his piece of memory to perform his calculations. So if we zoom in, what do we see? Well, we see something like that. Your process is taking this slice of memory. Of course, this is a super
mega abstraction. This is more complex than that. This is not all contiguous. And yeah, you know, that is more complex. Reality is always more complex, but we want to grasp the principles. So the idea is that we take a slice of memory, and inside that slice, all the action is going to take place. Of course, we're going to focus our attention in the stack because here is where all the action is going to take place, right? We have the main function that is calling another function that is calling another function, and so forth. You can
have call to malloc. So we have also the heap that is involved and other details, but initially, we're going to focus on the stack. So I think you have already all the tools to understand what is going on with pointers. Everything is happening in the RAM. Your process is a living being that is calling functions, one each other to perform a specific task. Everything has a specific address, okay? These addresses are pointers, my friend. So now we go deeper. Okay, my friend, we are ready to start our journey into pointers, and we are gonna start
with variables. Of course, pointers are just addresses to variables, so we have to understand super well this concept. Now, we have this frame of mind that the RAM is just a fancy notebook, right? In which I can write stuff in a binary fashion because we are dealing with electricity and with binary entities, namely transistors that here are just light bulbs. Now, let's make a simple example. As you can see here, I have a little main program that is gonna declare a variable nb and assign the value 42. So what is happening here with this code?
Very simply, I'm going to write somewhere in memory in my RAM this number 42. So I'm gonna turn on and off the bits accordingly. For the number 42, I have 1010, as you can see here from the least significant byte, okay? So I have, long story short, four bytes, depending on the machine, but generally speaking it's four bytes, allocated, and I have the value 1010, which is inside these four bytes. nb is gonna be the tag for us, for humans, for programmers, but from a computer standpoint, we have only addresses, of course. So TLDR, we
have somewhere in my memory some bits which are turned on and off. Now, let's watch again this little program from a stack perspective, because you know, right? When I have a process, I have my operating system that is giving me a space in memory, the process memory layout, and all the action is gonna happen inside the stack, the heap, the data, and the text, and all the segments that I showed you in the previous video. So the stack discipline, namely the allocation and the allocation of all the functions inside my process, happens inside the stack,
of course. So here I have the allocation of my main. Inside my main, I have the creation of this variable nb. So I have a local variable, which is inside the function frame of the main function. A function frame, in this case the main function frame, is the specific space relegated to the main function. So the main function has this specific frame to perform its stuff. So inside this function frame, I have the creation of my variable nb, okay? Let's zoom in a little bit so we can understand better. Here I have my stack, and
inside my stack, I have this frame in which my main function lives. And inside this frame, I have some bits which are turned on and off. So that's somewhere in memory, is the space given by the operating system, where the process can do its own business. Of course, now we are inside the main function frame, because we are inside that function. Now, you see that the variable nb has a specific address, right? I want to stress out that this is not the real deal. This is not the real address, physical address. This is not what
you find inside your real RAM. Indeed, this is a virtual address. Basically, we have the operating system, which is a juggler, is a magician, is doing all the tricks to optimize performance. As long as we are concerned, we don't really care. For us programmers, this is the address of the variable. Then we have some other pieces, like the memory management unit, that are going to make all the necessary computations to match virtual and physical address. But we don't care. We don't care. We work on a higher level of abstraction. Our process really thinks that has
unlimited memory, thanks to this magic of the operating system. But let me repeat to you, we don't care by now. We want to stick with principles. So an address is an address. Now, let's watch together a little program so we can really observe the stack discipline and what is going on inside the stack. So here I have a stupid program made up of a few functions. So I declare in my main the variable nb, like before you just saw, right? So our program starts, of course, from the queen function main. It's going to declare and
assign the value for two to the variable nb. So we have four bytes in memory that contain the value for two, one, zero, one, zero, one, zero in binary. And then we reach the point of change value. So we have a function call. Change value is a function that essentially tries to change the value of the variable. Let's see what happens here. So once we reach change value, we stop the execution of the main and we allocate space for the change value function. As you can see, the stack has grown. And now we have this
new allocation. The stack counter intuitively grows from top to bottom for historical reasons, but this is indeed a growth. So we have the main function that is stopping. And now we are playing the change value function. So what is happening here? We have this function that is taking as an input nb, okay? So our variable inside main and it's trying, allegedly, to change the value of nb to another value, which is 1,337, okay? What do you think is gonna work? This little program, can the function change value indeed change the value of nb? Well, let's
try. So we have the execution of nb equal 1,337. And then we have the the allocation, of course, of the function because we have finished. A function finishes when we reach the final closing curly brace. Cool. Now we are back to the main. We can play again the function. And now we have the call to the printf function. The printf function, you know very well, has to print the value of nb. So we call the printf, boom, we have the allocation. But now, as you can see, the printf is printing 42 again. So my change
value function hasn't worked. Or is it? Namely, the function worked, but maybe I didn't quite understand what was going on, right? So what is the issue here? What is the problem? Let's try to understand and deconstruct this issue. So now we stop at the moment in which we were calling the change value function. So I have my main function frame and then I have my change value function frame, as you can see. So I have my variable nb both in main and in change value. But let's watch more carefully what is going on here. So
I have my frames, right? My main function frame and my change value function frame inside the stack. As you can see, I have nb both in main, right? And then I have also nb which is in my change value function frame, right? So immediately what can I understand? Well, I have two variables. They have the same value, the same name, but they are different. They are in different spots, in different places in memory. So nb in main is a different variable respective to nb which is in change value. And this is tricky, right? Same name,
same value, they are the same. Nope, it is not the case. The thing is that when we call some functions, the arguments are passed by value, as we say in C. Passed by value is a fancy way of saying, well, the variable is going to be copied. So what is really changing change value is a copy of the variable. It is not the variable which is in main, you see? And if you watch carefully, we have the addresses which are different. They are pretty close because they both live inside the stack. And one function is
just calling one another, so they are very, very close. But nonetheless, they have different addresses. That's the main idea. And to make it clear, nb in change value, the arguments name nb could have been different. Like, for example, fubas. We don't really care because they are different things. So what is happening here? When I change value nb 1337, what is really happening? Well, the function is changing the value of its own copy. And it's not changing the value of nb which is in the main function, you see? That's the catch. So let's watch what is
really happening. We have our variable in change value that, boom, is changing its value. As you can see, I've changed the binary configuration. Now this binary state is representing the value 1337. But then what is going to happen? Well, we have the deallocation of the function change value. So boom. And in the main function, I call again the printf. But the printf is getting the old for two, right? Because I've changed the value which was in another stack frame. So nothing really changed. So printf simply keep using the usual nb for two. And the printf
is not aware that we don't understand pointers, my friend. So what can we do if I want to change nb which lives inside the main function frame? What is the trick here? Well, my friend, that's where pointers come handy, you see? Let's rewatch again the animation. I have my main function. Inside the main function, I have the variable nb. Then instead of calling change value with nb, I'm doing something different. I'm using the ampersand operator. This ampersand operator is giving the address of the variable to the called function. In this case, change value. So I
have to change also the change value because this time we are getting different kind of inputs. Look carefully. Change value is taking this time a pointer to nb. We use the asterisk operator. And as well, nb equals 1337. We have to change a little bit. We have to say at nb. So I'm gonna dereference. This indeed is the dereference operator. We will see later. So this time, the input, the data type that change value is taking is not anymore a variable. But it's a pointer. It's this big boy here. He is eight bytes, generally speaking,
but depending on the machine. That is able to point indeed somewhere. So this variable contains the address of another variable. And if you watch carefully, you see the binary configuration of this variable, which is exactly the address of nb. So this pointer is a special kind of data that is able to point somewhere in my memory. We can make indeed reference to something which is not here, not in my local frame, not in my local function frame. Indeed, when we say dereference, we are meaning reference to something which is not here, which is far. Think
about the word, I don't know, deportation. Bring someone far. That's the idea. Here we'll make your reference to something far, the reference. And when I say star nb, I'm saying, hey, go in the address pointed by nb and put there the value 1337. Okay, so we have this condition this time. Let's watch the stack frames. As you can see, I have nb and both nb. So I have my main function frame, and then I have my change value function frame. But this time, thanks to nb, what I'm gonna do, I'm gonna dereference this pointer, boom,
so I can access the nb, which is in the main function. And then I just do at nb, I can read in this fashion, asterisk nb, is equal to 1337. Now, look carefully what is going to happen, boom, I'm gonna change the value there. Okay, so thanks to the magic of pointers, I'm able to go somewhere else and change values. This, my friend, is called passing a value by reference, not by value, but by reference. So we give to the function a specific address, a specific place to watch if we want to do its business.
So pass by reference, which is very different from passing an argument by value, right? You just so. So we have the change value function that gets the allocated when we reach the final curly brace, and then we call again the printf. This time, the printf is getting as an input nb, which has been changed. You see, I have 1337. So everything works, thanks to the power of pointers, thanks to the power of dereferencing, making reference something which is not in my stack frame. And we are ready to rumble. You have the tools to understand everything
about pointers, okay, my friend? So here, my friend, I have the actual code, as you can see, my main function in which I declare my variable nb equal 42, and then I call my change value, the function you just saw. I call my change function here, I call the variable in full bar just to stress out that this is gonna be different, this is gonna be a copy of nb. And you just saw that this program doesn't work. Let's try out. So I'm gonna run my program, and I get 42, as you can see. R
is just a function that I created, it's just compiling, doing all the checks, and so forth. It is like GCC and then launching a.out, it's the same. So we watch again the code, and this time we're gonna tweak a little bit. First of all, I'm gonna tweak the prototype, so the function is taking an address as an input, so I have to use the star. I will explain later why here I don't have the ampersand, because this is a little tricky, the syntax of pointers is kind of tricky, I will explain. So here I have
a pointer, here I give as an input an address, so ampersand nb, I'm giving the address of nb, and here again I have to use a pointer here, and here I have to dereference. The difficult part in my opinion is that declaration and dereferencing are the same, right? We have the same syntax. This is tricky, but no, not really, I will explain to you. Now we're gonna run again this program, so I'm gonna run, and you can see this time I have 1337, it works. Now let's watch again the classical example of using pointers, and
this is the swap function, right? I have two variables in my main, which are 42 and 1337. I'm gonna print their values, then I call the swap, and then I'm gonna print again, as you can see, the swap dereference to the two variables, ampersand a, ampersand b. So my swap is receiving the addresses of a and b, as you can see from the signature of the function, right? I have as an input two pointers, pointer n and pointer n1. Then I do my swap routine, which is just making a temporary variable in which I can
stock the value of n, then I say at n is equal to at n1. Basically this is a stupid swap algorithm. So let's try to launch this, and you can see that it works perfectly. So you understand super well by now what is going on, right? With these pointers. So now the tricky part, declaration and dereference have the same syntax. Why doesn't C have a better syntax for doing this? Namely, a different syntax for dereferencing and respectively another different syntax for declaring. This is confusing. Initially it was for me a little confusing. Keep in mind
this principle. I have that declaration tells me how to use. What does it mean? Well, let's try to understand together. I have here a declaration of a variable int n, right? Classic declaration. Here I read in this fashion. To get an integer, just use n. That's why I read, even this declaration. Let's try another. Int star n. What is written there? Well, it is written that I have a pointer to an integer. So I read as a programmer to get an integer, just dereference n. You're starting to understand, I guess, right? Here I have n3,
which is an array of three integers. But here I read in this fashion. To get an integer, you have to index inside this array a value. So in an expression, n square brackets 0 1 2 is going to heal an integer. The last example is a function. For example, you have a prototype foo int n float n1. What do I read? Well, I read that to get an integer, just call foo with the related inputs. So TLDR, the notation of a variable declaration is telling me what you must do to something, to get something of
that specific data type. Let's apply this principle to pointers. Here I have a declaration of different pointers. And you can see I have int n, this is just a variable, then I have star p n, and then I have star star p n. What do I read here? Well, I read to get an integer, use n, use the identifier, the variable identifier n, or you can use star p n, or you can use star star p n. They all will heal an integer type when used in this way, in an expression. So in my view,
in my mind, a declaration is an how to use, okay, to get a specific data. So here, as you can see, I have many different declarations for different declarations. So I declare here a pointer, a double pointer, a double level pointer, and a three level pointer, okay? Here, given the principle I just told you, I see that I have to use this identifier, variable identifier, variable name, call as you want to get an integer. Here I have to use star p n to get an integer, and so forth. Right here, I have to use three
times the start on this p p p n to get an integer. So if I want to get, for example, an integer from this, what shall I do? Well, I have to use two times the, let's delete this, and let's do that. Print off, percent d, new line. I have to do, to this boy here, triple the reference, p p p, a triple the reference p p p n, like that. And this is going to heal an integer because here it's written. If you triple the reference, this boy, you get an integer. If you single
the reference, this boy, you get a double pointer to integer, you see? You get the catch. Let's try if it works. So I run the code, and as you can see, I get 42. I get the number 42. Perfect. Because I tripled the referenced this. Now, if I do something like that, I do p, the uniform pointer, is equal to p, another pointer. So what do I do? I double the reference this. Why a double the reference? Well, because if I double the reference this boy, I get a pointer to an integer. And here I
use pn, which is what? A pointer to an integer. So I'm going to use directly with node referencing because this, if I use this, I get a pointer to an integer. I'm not the reference. You see the catch, my friend. Let's watch if it works. Okay. Here I have some controls that I made. So I'm going to do like that. GCC, and then launch. Boom. As you can see, they are equal. They are the same, right? So just keep in mind what I told you. This is an instruction manual. It's telling you how to use
this pppn to get a specific data type. If you do a triple the reference, you get an int. If you do a single, you get a double pointer. And if you do a double the reference of this, you get a pointer to an integer. You just saw here. That's the idea. Okay, my friend. So I guess these declarations are not going to be difficult anymore. Now, another question. Which are the advantages of passing pointers as arguments compared to passing by value? This is a good question, right? Let's watch some pros and cons. Of course, everything
in computer science, like in life, has some pros and cons. So we start by passing by value. Of course, passing variable by values are called to you that they are just copious. It's easy to understand, right? It's super easy. You pass a variable, and the other function is doing its business to that variable, to that copy, and I have nothing to do reference. Super simple. Of course, this is safe because passing only copies is going to prevent some other functions to change value somewhere else and making the code overall unreadable. And yeah, you are sure
that the called function is not going to mess up your variables in your local frame. The bad part is that we have some performance overhead, of course. When I pass an object by value, this object has to be copied. So keep in mind, if the object is very, very big, you have a very big memory overhead. Now, we cannot really pass arrays by value. They're not copied because we have a phenomenon which is the array decay into a pointer. I will explain later the relationship between pointers and arrays. But if you have a struct, for
example, a struct has to be copied. So you have every time to every function to copy this big boy. You can very easily understand that this is a big overhead. So another negative part of passing by values is the short reach, right? You cannot do what with pointers you can. So this can be good, of course, because you have safety, but it can be also bad because you're tied to your local frame. So this can be a plus or a minus. Passing by reference is efficient. So you can pass big data structures, for example, a
big struct, and you're going to pass only a reference. So in the call function, you don't have to copy everything. Another plus is that you have direct access to other places in memory. You just saw with the swap function that we can go to change stuff in other places. And this is useful if you know what you're doing. And yeah, this is a way to return, quote unquote, return multiple values. Because with a function, you're tied to return only one value, right? You have returned the value. Here, we are able to change stuff in other
places. So we can make functions that have a lot of functionality. Side effects, of course, is the one I just told you before, is that you can mess up a lot of your code. You can create code which is impossible to read because you have many functions that are changing parts of other code. And overall, it can be a big overhead to have many pointers if you write bad code. And of course, this is complex. Taming pointers is indeed very complex in C. This is one of the most difficult, if not the most difficult arguments
related to C programming. So now, my friend, we are going to a website which is very, very useful, especially if you want to learn pointers, because this is a visual tool that allows you to visualize directly pointer. You just type ctutor on Google, and you go into this website, then you just pass some code. Like for example, here, I have this sample code that now we're going to check together. So let's watch in the terminal. So we have colors, I have created a little struct, which is fat boy, it contains a bigger array, bigger array
of 50 elements, so not that big. And here, I declare one instance of object, right? So I have my fat boy, then I just put somewhere randomly in my array the value 42. And then I need just to find this 42 in my fat boy, okay? So I call the function, find42, passing as an argument fat boy, you see? So I have my find42 that is taking what? Well, it's taking a copy of fat boy, you see? It's not taking reference, these are passing by value. And then I have a simple routine that is searching
the position of 42. So it's searching where in my fat boy 42 is, okay? Now let's watch here the execution in ctutor. As you can see, I have this big boy here, fat boy, that is a big array, you see? A big struct. And when I call find42, I have a copy of this. So I need to pass the entire copy. And this is bad, right? Just keep in mind that you can have many, many functions. This, of course, is going to impair performance because if you have to copy every time all the thing, it's
going to take time, even though computers are really fast. Now let's tweak a little bit the code. I'm going to change this. So I'm going to use ampersand fat boy here, as you can see. So I give an address to the struct. And here in my find42, I'm taking a pointer. So here, now I have to tweak a little bit, even here, the code. I need to use this special operator. This is for structs, but this is not important now. And here we are. Now I just use a loop inside equals zero, i, minor, dense
size array, plus plus i. I'm going to do fat boy v in position i is equal to i, okay? This is just a simple loop to put some values inside all the cells. So let's run it. Here, as you can see, I have some random values. Then I call the find42 giving as an input the address. And then I call the function. Boom. Now look carefully. This time, you see, I don't have the copy. I just have a pointer. I have a pointer to my big array. So my find42 here, my function, doesn't really need
to have an entire copy. Just have a reference. So my find42 can make a reference to a variable which is not in its frame. It's somewhere else. It's in another place. Indeed, it's in the main function frame, you see. Here with this website, you can clearly see the stack and the heap. So here are the functions allocated in the stack. So it's going to do its algorithm in its own frame, looping inside the variable which is in the main. A certain point is going to find 42 in position 14, which is this one. Of course,
this is a star. Just randomly, 42 is also a star because if you watch the ASCII code, you will see that 42 is indeed a star. And my friend, you understood why passing a value by reference can be more efficient. Well, you don't have to copy all the stuff all the time. These, of course, make more sense once you have big structures. For example, fatboy here, which is a big structure, relatively big. Other question, why pollinators to different data types have the same size? This is a good question, right? Now, I'm going to propose to
you a little parallel, which is illuminating, in my opinion. So if you want to search the address of, I don't know, the Empire State Building, or if you want to search the address of a random dude selling out dogs at Central Park. We're going to get a bigger address if you want to go in the Empire State Building, given that this is much, much bigger than the selling of dog cars. Well, of course, no, my friend. An address is just an address. This is something that is telling where something is. It doesn't hold that the
object pointed by this address is big or not. This is not the point of an address. So this is the idea of pointers. If we are pointing something in memory, that can be, for example, an integer. For bytes, generally speaking, depending on the system, we have an address of eight bytes in my machine. If we want to point to, I don't know, a short, we still have a pointer of eight bytes. If I want to point to, I don't know, a double, which is pretty big, it's eight bytes as well in my machine. I still
have the same pointer. I still have the same size of my pointer. You get the point. Because all the data have an address which is of a specific size. So keep in mind these parallel that I made with the Empire State Building and the selling of dog and dude cars. That's the same idea. That's the same principle. So here, my friend, I have a little program that is showing you this. I have a chart, a short, an int, a double, and then I have a type big, which is just a struct that contains a big
array. Here I have three, six, one million integers. So I have four megabytes. And here I simply print the size of, this is an operator that is giving me back how many bytes something is big, of the address of C, of C, H, of I, D, and big. So the address of all pointers do these objects here. All right, let's try to launch this code. I have eight, which is the size of the pointer to every data type. To a chart, which is the smallest, to big struct, which is the biggest. I have all the
times eight. Now, another question. Given pointers have all the same size, we just saw that. Why do I need to specify the pointer type? This is another good question, right? Indeed, when I declare a variable I'm telling the compiler, hey, dude, I want an integer, so give me my machine four bytes, or I want long, give me eight bytes, or I want a chart, give me one byte. They are all integers at the end of the day. They are stocked in memory as an integer. So here I'm just saying, hey, I want eight bytes, or
I want one byte of the integer type. I can do the same line of reasoning with floats, but you got the point, right? So the type is telling us even how big an object is. Of course, this is a simplification. At the end of the day, it's the compiler that is deciding how big an object has to be, but anyway, just linger on the principle. You decide the data type, depending on the size. For example, if I want a very little number that is ranging from, I don't know, from zero to 100, I can use
a chart. I can use a chart as a one byte integer, not only for charts. This is an integer of one byte, so I can really use it for a little number, if I want to use. So I can very surgically, with data types, decide how much memory I want to use. This is pretty useful, right? When you want to have super control of what is going on. Indeed, the C programming language is getting you superpowers in this domain. On the contrary, you have pointers that have all the same size. So what is the point
of telling me, okay, this is a pointer to an integer rather than a pointer to a chart? So here I want to repeat to you again this point, that the binary configuration of 42 is very, very different from the binary configuration of 42.0 float, right? Because we have a different encoding of the binary. We have exponent bits in the real data types. On the contrary, on the integer data type, we don't have this kind of encoding. We have simply the number in a choose complement fashion. So keep in mind this because we're going to do
a little trick related to this idea. So now I have a little program that we're going to check to understand why a pointer has a specific type. I have a declaration of a variable, which is an integer, int nb, and then I have a declaration of a pointer. You can see int star ptr. This means that if you dereference ptr, you get an integer. nb gets the value 42, and ptr gets the address of nb. Then I call my printf. So I want to print the value pointed by ptr, and then I do something peculiar,
which is this casting. Okay, I'm gonna cast ptr to a float star, float pointer. What is this casting thing? Well, the best analogy that came to my mind is like the real casting, when we want to decide which actors have to play in in our film, right? Well, that's basically the same idea. When we do a cast, we are assigning a role to a pointer in this case. So we are assigning the role of a pointer to a float to my pointer. So here I'm selling to ptr. Hey dude, I know that you are a
pointer to an integer, but in this time you're gonna play the role of a pointer to a float. So we're gonna do another chuck, and we're gonna dereference again the pointer, but this time of a type float. So here you come, my friend. This is the actual program. As you can see, I have the thing you just saw in the slide. The only thing is that here I have an e, because I want to see in scientific notation otherwise the number is going to be too little. But we can also do like that. 0.60f. Okay,
let's try to launch the program. So I'm gonna launch. Look what happens. I have this 42, and then I have this 0, 0, 0, 0, 0, and then I have this value. What is that value, my friend? So you just go on this website, which is float exposed, in which you can play with floating numbers. So now let's try to do something. I write 42 here in binary, of course, which is 1, 0, 1, 0, 1, 0, of course, starting from the least significant byte, which is this one. So I have 1, 0, 1, 0,
1, 0. Do you see something that you just saw? 5, 8, 8, 5, 4. What do you see here? 5, 8, 8, 5, 4. 5, 3, 5, 5, 0. 5, 3, 5, 5, 0. Hmm. So you see what's happening here, my friend. Here with this cast, I'm telling a pointer. When you're gonna be the reference, read those binaries as a float, not anymore as an integer. So you're pointing to a float now, you see? So that 1, 0, 1, 0, 1, 0 is dereferenced in this fashion. It doesn't see any more, 42, but it sees
this very little number, which is this 5 elevated to minus 44. Here we can do in scientific notation, essentially like you did before, like that. And you will see the exact same value. You don't see all these zeros, right? 5, 8, 8, 5, 4, 5, 4. Of course, here you see a 4 because we have the value rounded and then elevated to minus 44. That's why you have all these zeros here. But anyway, you get the point. The pointer type tells the compiler how to interpret the binary, the spot in memory that it's pointing to.
So that's why we have a data type for a pointer. This is crucial all the times you want to get a specific value. Let's play a little bit with this code so we can have a super good understanding of what is going on. This time, I'm going to do a charm, for example. I'm going to cast to a charm. And here I'm going to write directly in binary. We can do that in C. We do 1 and 8 zeros. 1, 2, 3, 4, 5, 6, 7, 8. And here I write C. What is going to
happen in your opinion? Let's close. Let's run. Okay. I cannot do because there's a parameter search, but I can't. These are just controls, flags that I put. Okay. I launch. I get the value 256, but I don't see any char. Why is that? Well, because when I reference this pointer, what I'm going to see is only the least significant byte. A char is one byte. So when I reference, I get the zero, the null char. So that's why I don't see anything. Here, if it was, I don't know, a short. A short. And here I
just put D. So you are going to see 256 as well, because when I reference this pointer, this is going to see this second byte, if you want, because the short is two bytes. Depending on the data type, we are going to read the bytes pointed by the pointer differently. This is not the full story of this type of pointers. We have C that is very consistent and regular in its approach to address arithmetic. Its integration of pointers, arrays, and address arithmetic is one of the strengths of the language. You will see later that there
is a big integration related to arrays and pointers, making it really tricky to differentiate between arrays and pointers. You will see. But keep in mind that here it is written. We want pointer arithmetic to be equal to index addressing of arrays. So let's make a stupid example. Don't pay attention to the binary configuration of the RAM. This is just random. I want just to make a point here about pointer arithmetic. So I have my pointer pointing to a char, a pointer to char, and then I'm going to print every time the value of this pointer
and the address, doing some arithmetic. As you can see, initially I want to print PC, pointer to char, its value. So I'm going to print that. Then I'm going to do PC plus one, boom. So as you can see, I advanced the pointer by one. Now the pointer is pointing one position more. Then I do pointer C plus two, so I advance another position. And then pointer C plus three, I advance another position. Now we say, well, that's easy, right? I have the pointer that is indeed advancing one position. But this is just the case.
The case is that a char is big one byte. So that's why we are advancing one byte at a time. I want you to pay attention to the fact that a pointer is just a variable. As you can see, the binary configuration of the pointer is changing as I do pointer arithmetic, because this is just a value that can change. Okay, so the formula to make pointer arithmetic is this one. I have the pointer plus n per size of type pointed. So that's why it's important to know the type of the object pointed. This time
I have a char, so it's easy. And let's say is three, the size of char is one. So I have the address plus three. Okay, now let's watch another case. This time I'm pointing to another data type, which is a short. For example, we're going to change a little bit the code, we're going to change the value. And now what is going to happen this time when I add one to PC? So look carefully, I have this boom, I have the advancements of two bytes, and not only one. Why is that? Well, you understood, because
a short is two bytes. So when I say PC plus two, I get boom, an advancement of other two bytes. So four bytes respective to PC. So that's what is going on, my friend, you understand super well that the data type is important. Also, when we do pointer arithmetic, you see, so this is going to discipline how pointer arithmetic is going to work. And you will see that this maintains a highly consistent parallel between pointer arithmetic array indexes. Indeed, it's like, I'm taking the object at position x. So this is just the code that you
saw, we are going to start, for example, with an integer this time, I have a pointer to an integer, and we're going to perform arithmetic with an integer pointer, I'm gonna compile, and then launch. As you can see, we have an advancement of what? Of four, right? Zero, four, eight. Okay. Why is that? Well, you understand super well now, because an integer is for byte entity. If you do your short, like in the slide, so we change here to short, what is going to happen? Well, you already know, I'm gonna launch, I have an advancement,
which is from two to two, zero, two, and four. That's all there is, my friend. Other question, void pointers are confusing. Hmm, let's dig a little bit. When I say that a pointer is a void pointer, what I'm really saying, well, I'm not assigning, if you want, a role to my pointer, I still don't know which is the best fit for this specific pointer. So we can say that void is a generic pointer, you should definitely change the word void to generic in your mind. When you see void, just keep in mind that this is
a generic pointer. So tldr void star void pointer means we will let you know, right? That's what happens with actors when we don't know which is the best role for them. You can keep this parallel in your mind, it's pretty useful. Now let's see a practical example. Here I have a stupid program that is going to declare in its main a value, the usual value for each row. And then I have a function that is called, which is print data. So this function is able to print that specific data, given a specific char, for example,
I, F, or C for integer, float, or char. The print data function is taking as an input, avoid star data. So we have a generic data that we're going to interpret inside the function. So if we have the char I, we're going to interpret the data as an integer, F as a float, char is a char. So you see that inside the print data function, I'm assigning the role to the void generic pointer. Okay, let's watch the code. Okay, here we are. As you can see, I have my integer value here. And then I just
print the value every time I assigning the role to my generic data pointer. So I'm going to run the code. And as you can see, I have 42, if this pointer is pointing to an integer, the value which you saw before, which is the binary configuration of 42, if it was a float, and then I have the star, if it was a char. You understand everything related to this topic, I guess. Now, when in practice we stumble upon these void pointers, well, when you use the malloc function, you see, how many times you have used
the malloc, namely requesting at runtime some space in memory. Well, the malloc is returning a chunk of bytes in your memory, but it doesn't really care about the data type. That's upon you. You are going to decide the specific data type, the specific role that the pointer has to play with the casting, right? Every time you use the malloc, you're going to get a void pointer. Now, my friend, given that we are talking about the malloc, I want to show you another little program which can be illuminating about the use of pointers. So here, in
my main, I have a declaration of a pointer, right, to an integer, and then I assign the value to this pointer, the return value of foo. Foo is a function that is returning a pointer, as you can see here, and essentially is returning a pointer to a variable, which is a local one. It's returning the address of n, which is a variable inside that. Then what I'm going to do, I'm going to use untaff to print the value pointed by the pointer. Okay, what do you think? This program can work. Let's watch out. So I
compile and immediately you see that I have a warning. I have a warning address of stuck memory associated with local variable and returned. It's tricky. Then I'll launch, and it works. I have my 42. But now, let's try to change a little bit the code. I'm going to uncomment this other function, which is bar, and this function, very simple, is going to declare a variable and assign a value. That's all there is. That is going to happen in this code. Let's try to launch and then try to deconstruct. So I'm going to compile and launch.
I get, first of all, 42, and then I get 1,337. What happened? I mean, here I never changed the value of the pointer, right? It has always been the same. Now, my friend, the issue with this code is very simple. So when I call the function foo, I have the allocation in the stack of this function, right? So my main function is going to stay idle, and then foo is going to perform stuff. Essentially, it's going to declare this variable 42, and then it's going to return address to that variable. Cool. After that, what is
going to happen? Well, I have the foo function that gets the allocated from the stack. So I have a variable, which is in a place in the stack, which is not allocated anymore. It's deallocated. Then I call my printf. I have 42, because that place in memory is not overwritten by other functions. Then what do I do? I call the bar function. So I have another allocation just above the main, like the foo before, and the bar function this time is declaring another variable. It's declaring this variable here, and assigning another value. So the address
that is the one of the previous variable n, which was in foo, now is the same address of nb, just by chance, right? Because we know how the stack routine, the stack discipline works. We can, in a nakish way, assign another value to the same address. So that's why in the second printf, if we print from the same address, we get this other value here. So now you understand very well what is going on here. So why the malloc? I'm going to say int pointer. I declare a pointer to an integer, and here I do
n is equal to malloc, sides off, int, okay? And then I say at n, so id reference, this address, I say 42. And then I simply return n. I return a pointer. So this data now lives in the, and it's not going to be overwritten, because in the API we don't have a nip discipline. We don't have an allocation and the allocation of stuff. So let's watch again the code, and this time I'm going to get two 42s, of course, right? I think that here it's useful to see visually. So we're going to copy this
and paste in ctutor, so we can see what happens. So I'm going to run. I call my function foo. As you can see, I have another stack frame, and then I call my malloc. As you can see, now we live in the heap. We're pointing in four bytes which are in the heap. Assign there the var 42, and then I'm going to return this pointer. As you can see, I have this pointer in the heap that gets returned to the main function. Boom. So now I have a reference to my 42, and when I say
print what is inside pn, I'm going to read 42. When I call the function bar here, and I get the variable declaration, I don't have any arrays of previous values like before, right? I hope you understand this point. But no, nothing, because the heap is there, idle, nobody's touching it, and here we go. So the second time we're going to print again 42. So that's all there is. And this is a thing by which the memory in the heap is useful, because this is memory that is living idle by the stack madness that is happening
allocation and the allocation stuff, okay? So this is a silly example of the malloc function. Now, if you watch manual malloc, we are going to see what I used to tell you, that every time we are going to receive a void pointer from malloc, then it's up to us to cast it to the specific value we want to have, okay? Another important thing that you have to keep in mind is that you cannot, of course, dereference a void pointer. For the reasons we just explained, right? What is the role of this pointer? I don't know
how to read the bytes pointed, because we don't have a type. So look at this example. I have an integer n, and then I have a void pointer in which I assign the address of n. But when I try to dereference it, you see, I have a mistake immediately. I'm going to say, hey, I actually don't know which is the data type pointed out, shall I read the bytes pointed? So if we compile, I'm going to get an error. Argument type void is incomplete, of course. And you understand super well by now what is going
on. Now, I want to add some more confusion to your life, okay, with these pointers. And this is the question of pointer arithmetic with white pointer. Do you think it's feasible? Well, C standard doesn't allow void pointers arithmetic. Void indeed is a no-type, so it's kind of tricky to understand how to handle arithmetic, right? Because you know that arithmetic depends by the size pointed. If I'm pointing to a short, I'm going to jump two bytes by two bytes. But it's kind of tricky to get the size of a no-type, because void is a no-type. Long
story short, we can do pointer arithmetic with white pointers. What is going on here, my friend? Well, we have a GNU extension. I can explain better. A GNU extension refers to additional stuff that you can do, additional features you can perform with the language beyond the standard, beyond the standard scene. In this case, we have an extension which is called pointer arithmetic of void pointers. Long story short, we are going to treat a void pointer as a pointer to a byte. This makes sense, right? Because when we have a void pointer, we are implying that
we have a pointer to a byte. I just have a generic pointer to a byte. So to prove you this pointer arithmetic of void pointers, I have this little problem, my usual variable 42, and then I assign the address of 42 to ptr, which is a void pointer. Then I do some pointer arithmetic. I'm going to print the value pointed by the pointer. I do a typecast just to make the reference possible, because I cannot reference a void pointer. So I'm going to reference as an unsigned char. Why is that? Well, because an unsigned char
is a byte, if you think about that. And then you hear the magic. I'm going to do ptr++. So what? Is it feasible? ptr++. Let's launch the code. Here I'm going to compile with all the flags that I have. As you can see, I have error because I'm using the flag w error. This is going to make of every warning an error, but this will work. Arithmetic on a pointer to void is a GNU extension. Basically, it's telling me, hey, this is something more. This is not the standard C. Now I compile with arithmetic, and
then I just launch. As you can see, it works. I have just the values that are printed. Let's do, for example, 256. I will get 0 1 0 0. Let's watch it. 0 1 0 0. You see, I understand why I got 0 1 0 0, because I have only 1 1, which is the ninth bit. The ninth bit is the least significant bit of the second byte. I hope you understand this point. So with this GNU extension, it is feasible, but it is not a standard C, so it is not suggested to use. But
now you know that you actually can. We already covered a lot of questions, which are kind of tricky, right? And now we are going to go a little deeper. Okay, my friend. Now I think you have a very good understanding of what is a pointer. Basically, it's just an address of a variable inside your RAM, inside your memory. Now we enter a little bit a murky area. Indeed, we have to understand what is this correlation between array and pointers. This, in my opinion, is the trickiest part related to pointers. So let's start from this definition.
The name of an array is a synonym for the location of the first element. So basically, the name of the array is an alias. So to a programmer, an array identifier is almost, and this almost is very big, just a pointer, because in most cases, arrays decay into pointers. Decay is a fancy word that means loss of type and loss of dimension. And yeah, I know, my friend. Tricky. But I'm going to try to make all of this as easy vanilla as possible. So what we're going to do now, we're going to create a program
that is going to be self-explanatory about this dance between array and pointers. So the usual boilerplate, so include standard input output, and then as usual, my main function. So int main. Okay, you perfectly know that to create an array, you just do that, right? You just say, for example, let's pretend we have an array of integers. We say int array, which is the name identifier of the array, and you can say 10. So this is going to create an object of 10 integers inside your RAM, more precisely inside your main stack frame. Now, I told
you that at the end of the day, the name of the array, the array identifier, is basically a pointer to the first element. If I do something like that, printf b new line, and I say r, I'm going to get the address of the first element. So to make the prove, I can say b is equal to b, and I use here the address of array, the first element, array zero. Do you agree? This is the same, right? This is equal to this. Let's try to launch this program. So I'm going to compile a very
pointer, and then I'm going to launch. As you can see, they are two addresses, and they are equal. So the name of the array is this one, and this is the address of the first element. So you can see that they are exactly the same. If I say the name of the array, I'm implying the address to the first element. Cool. So here I want to create my fake array using only pointer notation. So first of all, I have to create a pointer. So in ptr, like that. So this is a pointer, and what shall
I do now? So this is a pointer to an integer, essentially like array. Now the key difference that it's immediate is that when you use this array notation, you are allocating in the stack some memory, right? You are allocating essentially 10 per 4, so you're allocating 40 bytes. This is an object of 40 bytes. This is not happening with a pointer, right? I have a pointer that is pointing to an integer, a random integer. So we don't have allocation, but we can have a trick to do that. We have a function in it which is
called aloka. Not a lot used because it is not suggested, but it exists. So the aloka macro allocates bytes of space in the stack. So you see, that's the key difference between malloc and aloka. We are in the stack. We are allocating in the stack. So we have this function that is able to allocate some space in the stack, essentially like an array, right? So now I can do something like that. I can do aloka. Of course, I have to include my standard library. I call aloka of what? Size of int per 10, okay? So
aloka is going to allocate in the stack 40 bytes, essentially the same thing that is happening here. Cool. So now I can say at ptr plus zero is equal to 42, as well as some other examples. So I can say at ptr plus one and and at ptr plus two is equal to another number, and minus 21. Let's do the same with the array notation that you know pretty well. We have array zero equal 42. I'm going to copy this and pass it. And then I do array in position one is equal to the same
value, as well as for the last. Okay, we have something which is almost equal. Do you agree? Now let's print the values. So I do a loop for int i equals zero, a minor, then 10, and then plus plus i. We are going to print the value which is in the relative position, okay? So we have array in position i. We do the same essentially for my fake array, only I use pointer notation. So at ptr plus i. Okay, of course, I have only three numbers. The other numbers are going to be random, but we
don't care. Let's try to launch this code. So I'm going to compile with all the flags. And as you can see, immediately I have this warning that is telling me Alokka is discouraged. There is no way to check for failure and some other details, but we don't care. This is just a sample to understand principles. So I'm going to compile normally and then adjust launch. You see, it allegedly works. Now let's make the code more clear. In the middle here, I'm going to put some spaces to make the code more readable. Okay, put string, two
spaces plus the one from put string is going to be three. Okay, so I'm going to compile normally and then I'm going to launch. So here it comes. I have my first array and then I have my second array. So as you can see, immediately the first three numbers are the ones, essentially like the real one. Cool. Now it is kind of the same. Let's try to find some differences here. First of all, I would like to increase directly ptr. ptr is just a simple pointer, a simple variable that I can change. So I'm going
to do directly ptr plus plus. I can remove the braces. Of course, they are not necessary. So I remain with this expression here. Of course, I have these operators. The plus plus is going to be performed before because I have associativity that comes from right to left when we have unary operators. But nonetheless, here I want to do the same. So I'm going to do not array i. I would like to try to increment this and the reference because we know that the name, the identifier of the array, is equal to the pointer to the
first element. We just saw that. Let's try. So I'm going to compile and immediately I get this mistake. Cannot increment value of type array of integer of 10 elements. So I cannot increase directly the value of this identifier. Strange, given that this is just a pointer, right? So this is one key difference between these two. I cannot do that. On the contrary, I can do very easily with pointers. Let's try to compile and launch. As you can see, it works correctly. No problem. So to make it more equal, I'm going to try to do something.
I'm going to say this is a pointer, but this is a constant. So here is written. This is a pointer constant to an integer. So now if I try to compile, I will get almost a similar thing, right? Because I have the constant qualified type. So basically, I cannot perform pointer arithmetic directly on the pointer itself. Basically, I cannot change the value of the pointer. Okay, so we managed to get a similar behavior if you want. Indeed, when you search online, for example, is an array just a constant pointer, you will find many people that
will argue, yeah, essentially an array is almost like a constant pointer. It is not the same, but you can consider an array like a constant pointer. Of course, this is not the case, right? If you want to go deep, they are not the same thing, but search by yourself is an array, a constant pointer, and you will find some good information. Now people are telling that an array is just a constant pointer. Because of this, you have this similarity. And indeed, that's legit. In your mind, you can consider the name of an array just a
constant pointer. But now I will show you that this is not quite the case. So let's start from the beginning, and let's watch all the similarities between the two. First of all, I'm gonna printf the address of the array, and I do person p. Okay, so I use the array identifier, which is array, that you know, that is basically a pointer to the first element. So I can do that. Okay, here I put this time my pointer, I compile, and as you can see, they're pretty close, right? They are equal 7930 7900. They both live
in the stack. Okay, they point in the stack. Okay, so this is the first point. This array and all these elements are living inside the stack. First thing which is equal in the stack. Okay, as well here in the stack. The other thing that you just saw is that we cannot we cannot increase cannot increase R, as well as we cannot increase this constant pointer, so cannot increase. So this is the same. Okay, we have two things which are equal. Now let's find something which is not equal. And here we're going to do sides of
operator, I'm going to use the sides of operator. And I'm going to print f percent zoo, the size of of array. So the size of R, I'm going to do the same here for my pointer. So size of PTR. So let's try to launch this time the code and let's watch if it is exactly the same. I'm going to compile opa. Here I get something different here I get 40 and here I get eight. Hmm, so this is a big difference. What is happening? Well, when I call here the size of my constant pointer, what
am I getting? Well, I'm getting the size of a pointer that you know by now that this is eight bytes, right, depending on the machine in my machine is eight bytes. On the contrary, when I call here size of on the array, what is happening? Well, I am getting the size of the entire array. I'm not getting the size of a pointer. You see the catch? I'm getting the size of array which is 10 per 4 because this is the size of all the objects. So I have an object in memory which is 40 bytes.
Here I have a pointer. I don't have an object of 40 bytes. I have a pointer and in every location in my stack I'm gonna put manually all these values. You see, this is a big difference already. Now let's try with another operator which is the ampersand operator. So I'm gonna copy this. I'm gonna print a pointer to the address of array and then just beneath I'm gonna do the pointer to array. So I want to do the same operation with my fake array. So using the ampersand I'm gonna do printf %p, so address of
ptr and as well ptr by himself. Why is it going to happen here, my friends? Let's watch carefully. Compile and I just launch. You see something peculiar. With the array I get the same address both when I use the ampersand here and when I don't use it. These are the same pointer, the same address but they are of different type. Here I have a pointer to array of type. In this case I have a pointer to an array of 10 integers and here I just have a normal pointer to an integer. So they of course
point to the same memory location but they have a different type. What does it mean? Well it means that if I perform pointer arithmetic with this I get a different result respecting to this one. Let's try it. So I'm gonna do that pointer arithmetic and this time as you can see I'm doing pointer arithmetic to this pointer and to this pointer which you just saw they are the same. Let's try out, gonna compile and launch. Look at that my friend. What do you see? They of course point out the same address but when I perform
a plus one I don't advanced equally. Here I only advance for a position. On the contrary here I advance much more. Indeed here I jumped one array position. This is of course hexadecimal notation so pay attention to that. So when I use the ampersand on the name of the array I get a pointer which is of a different type respecting to this name of the array alone. So I get a different pointer arithmetic. You recall from the previous videos I told you that the data type of the pointer is going to imply the way by
which we read data in the respective memory pointed and the pointer arithmetic itself. This is what is happening. On the contrary here what do you have? I have a pointer to a pointer and here I have a simple pointer. So if I do the same trick so I'm gonna do this and that and here I'm gonna say plus one and here plus one. That is going to happen. Compile. So I get those results. So this is the address of the pointer. This is the pointer itself. This is the address of the pointer plus one and
this is the pointer plus one. Of course this is not very well written. I should have written the output better but you get the point I guess. This is the address of the pointer. When I say pointer plus one I jump here. I jump eight because a pointer is eight bytes. Here on the contrary I say pointer plus one. I jump four because an integer is four bytes. So let's not dig too much into this technical stuff which is not easy and let's understand the overall idea. So I have these two objects an array and
a fake quote-unquote array that I created. They are basically the same. They both allocate memory in the stack. This is happening because this is an array. You know how it works. You have a location in memory. Here on the contrary I perform myself the allocation using this function which is not suggested but that's how it works. So it's gonna allocate 40 bytes in the stack. This is a constant pointer to an integer almost like this. So both are in the stack. Okay. I cannot increase array as well as I cannot increase ptr because this is
a constant. The difference is that when I use size off I'm gonna get the size of all the object of all the array. When I use size off on my pointer I get the size of my pointer because this is just a pointer. When I use the ampersand I get the address of the pointer. On the contrary when I use the ampersand on an array I get the address of the array that's typed so I get a pointer of a specific data type. In this case I get an address to an array int 10. So
we get these things which are not equal. And now let's understand why. Let's understand what is going on. We have this array pointer duality. The best analogy that comes to my mind is the wave particle duality right of quantum mechanics. We don't actually know if a particle is a wave or a particle because sometimes it behaves like a particle. Sometimes it behaves like a wave and that's all confusing. So in the same way we have arrays and pointers. Sometimes they are arrays when I use the size of operator and sometimes they are just pointers when
I just dereference the first element. It is just a pointer at the end of the day. So this is confusing. Indeed my friend there is this phenomenon which is called array decay in which the array basically is a pointer. You can perceive the array as a pointer and this is the message I want to convey to you. We don't want to get bogged down into technical details. We just want to write code. So my suggestion to you is that when you work with an array identifier much probably that is just a pointer to the first
element. Ira just created this stupid model, standard model of arrays and pointers by which we have an array that always decays into a pointer to the first element but not in all situations. For example when we want to increment the name of the array you just saw we cannot do that. We cannot increase the name of an array. When we use the ampersand operator with an array we get the address of the array that's type. We don't get an address of a pointer. We get an address that has a specific type that is the type
of the array int array 10 in the case before. So it's kind of tricky right? This array decaying is kind of a tricky beast to master. Now let's see what is written in the C standard. So except when I use the size of operand or the ampersand operand or a string literal to initialize an array an expression that has type array of type we have a conversion to an expression of pointer to type. So the array is decaying into a pointer that points to the initial element of the array. Okay so this is essentially what
is happening. Keep in mind the stupid model I've created because I think it's fairly useful. It gives you the idea of what is going on. So let's write together a little bit of code so we can understand better this idea. So as usual my main function on the top and then I create an array of 10 elements to integer like always. So I know that every time the array identifier is going to decay into a pointer to the first element but not always. Like if I do printf you saw before zoo size of v we
don't have it okay. Here the value is going to be 40 and not 8 right? But if I do something like that I do ptr and then I do ptr is equal to v. What is happening here? Well I'm not assigning an array to the pointer. I'm assigning a pointer to the first element you see. So here if I say v in position 0 is equal to 42. Here I can see that value just with the printf and I say at ptr. This is exactly the same right? So I'm gonna run. As you can see
this is the size of the object and this is the element in the first position accessed through the pointer. I see because here I have decay. Here I don't have decay. Why? Because I'm using the size of operator. So the decay as well doesn't hold if I'm using the ampersand operator you saw before. So I just remove this. Here I'm getting the address of the array which is equal to the pointer to the first element but is of a different type. This is the key difference and basically that's it. I don't want to give you
too many details because at a certain point you don't need to stress out too much. When you work with arrays you have an object allocated in the stack and this is different from a pointer. An array is not a pointer. The trick is that very often an array decays into a pointer. So we can basically consider an array like a pointer. The thing is so intertwined that basically I can use pointer notation like that to access elements inside my array. I want the object at position v plus 1. So I'm gonna do v1 is equal
to 42. But I access with pointer notation. Why? Well because this is a pointer. My friend this is a pointer. So I'm gonna run. That's the same. So what is at the end of the day the difference between array accessing in this fashion or in this fashion like that. Well there is no difference indeed. They are the same. The square bracket notation at the end of today is just syntactic sugar. It is easier to perceive what is going on with this way of typing but underneath the hood what is happening is that we get the
reference of a pointer. You see? Coming back to a previous principle that I explained in a previous video. We have C which is consistent and regular in its approach to address arithmetic. Its integration of pointers, arrays and address arithmetic is one of the strengths of the language. That's why pointer arithmetic works the way it works. It's basically array indexing. When I increase by 1 I'm pointer to a short. I'm increasing by true positions because I want to access the following short, the following object. So when the compiler sees this v square bracket one what is
happening is that it converts this to that. Okay? Plus one. So we can do something really murky now. We can do that. We can do printf the same but this time I'm gonna do one in position v. Oh my god. Do you think it's gonna work? One in position v. This doesn't make any sense but let's try. Well you see it works as well because what is the thing here? At the end of the day the compiler is going to translate this into this. So it's the same, right? At the end of the day this
is commutative. This is the same thing. So we can do really strange stuff because at the end we have the referencing of a pointer. Now let me show you some other quirks just to have fun not to learn this stuff because it's very bad programming. But you can do printf for example the char in a string literal. In a string literal that it is an array of char in C. So I do hello and I take the one in position three or let's do the one in position zero. Let's take the h. Do you think
it's gonna work? Let's run. So it's gonna give me back the h because this string literal is an array underneath the hood and this is array indexing. But I told you that this is just like pointer dereferencing so we can do this. Okay that's right. I don't know. As you can see I get two h's the same or I can do something like that. That's true and it's gonna be so bad to see. This is gonna give me back an l. H h l as you can see, right? Now you understood the trick, right? We
don't have to go any deeper. You can do super strange stuff given the nature of reality in C. So the message I want to convey to you is that arrays are arrays and pointers are pointers. They are indeed different things. But as Drake is telling us to Drake that's just a pointer. End of the story. Be Drake. Be cool. Because when you're working with arrays, unless you're using the size of operator or the ampersand of operator or so forth, you are handling a pointer to the first element. So in your mind you can think okay
this is a pointer to the first element. End of the story. So now let's try to respond to some questions. And the first one is why this array decay? Well it's fairly simple, right? If you reach this level of the course you know that when an object is the key to a pointer we have a reference to it. So let's pretend we have a very very big array and we are passing to a function. Well we are not passing a copy so the function doesn't have to copy all the the thing but it just has
a reference to it. So it's efficient in memory usage when passing an array to a function instead of creating copying. So when passing an array to a function we are now creating a big copy and this improves performance overall, right? So you see this little stupid program. I have my main. Inside my main I'm going to declare a big array and then I pass to foo. Foo is going to receive just a pointer even though you write a square bracket. This is a pointer. When I write a square bracket I just say keep in mind
that in the main I have an array and not a pointer but they are the same, right? You can do like that or like that. So the foo function is going to receive only a pointer to the array. It's not going to receive a copy of that. It's not going to copy all the array in its local frame. Of course here I can write in these two fashions. It's totally the same. So I compile and launch, you see? Cool. So adding this decay is going to improve your memory usage. This is flexible of course because
every array has a specific data type. I can have in the examples of before I have array int 10, array int 15, array double 30, you know? So for every array I would need an adoc function. I don't want to have many many functions that are dealing with specific array types. I have an array that is decaying to a pointer so a pointer is a pointer. So I can have a generic function that is able to perform its operation to every kind of array. The bad part is that I have loss of size information. Basically
when I pass an array to a function I don't know anymore how big is this array? So I need a little time to bring with me an anchor value which is the size, right? Every time I call a function I have this cumbersome size that I need to pass or I can use a sentinel value. For example interleaved inside the values in my array I can have a value, for example a negative value, given that my array has only positive values, that will apply as a sentinel value. But you have to engineer a way to
communicate to the call function that this is an array of a specific type. What is the deal here? Let's try with the code to explain why this is important. You see here I have my little code with a symbol in constant with a value 100 size symbol in constant. So I create my array which has 100 elements. Then I go to populate my array with random values. Then I want to call my function which is printElements and I give as an input my array v. Okay? Now printElements would like to print the values inside my
array all the values but it doesn't know how, you see? Because I have my for loop which has the exit condition when i is minor than what given that v here is only a pointer. You see? The catch. So here I need to pass the size. So we are gonna pass the size and here I'm gonna put int size. Okay? So here I do is minor than size. Now we can work, you see? To make it crystal clear. If I do printf zoo of v here I'm gonna get um like that size of v. Here
I'm gonna get eight and not the size 100. So let's run this code and let's try to see what is going on. 14 on course size. Any array of all the random values which are inside my array. The first one of course is the value eight which is the size of the pointer received. Here there is a little mistake. There shouldn't be size of int. So let's try to launch again and then here I want to tweak a little bit the code to make it more readable. Let's do modulo 100 one. So I have numbers
ranging from zero to 100 by launch. Okay now I have all the numbers that I want. And here at the beginning I have my eight which is the size of the pointer. Basically the name of the array that at this level has decayed into a pointer. So I guess you understand what is going on. Okay? All the time so I need to bring with me this size value. Otherwise the code function doesn't really know how big the array is. So like all the things we have pros and cons, trade-offs. Now the question that you just
saw before so I'm not gonna repeat. Why array index five is equal to five index array? You know because at the end of the day we are de-referencing a pointer. So very shortly the C standard defines the square bracket operator like the referencing right? So array and index expression is equal to pointer and offset. That's totally the same. So if I say array five I'm saying at array plus five and if I say five array I'm saying at five plus arrays. So you see I'm just inverting the values and of course this is commutative and
that's no problem at all. But you understand this idea right? Okay we are finished with this duality array and pointers. I try to explain as the best as I can but I understand that this is tricky. This is tricky. Keep in mind this idea. An array is an array but most probably is going to decay into a pointer to its first element. So you have this standard model of array and pointers by which an array is just a pointer in your mind. And believe me every time I write code I think I basically forget that
an array is an array at the end of today. So for any practical purpose they are the same. Now pedantically speaking you understand that they are not. Okay my friend now we understood all the big principles related to pointers. Now we go a little deeper. So we're going to do now pointers to pointers to pointers to pointers and so forth right? We are going to understand how these work okay? The thing is that basically you can create arbitrary complex objects right? Arbitrary complex pointers. Let me tell you that in real code you don't very often
stumble upon more than three levels pointers. On the contrary double pointers are totally normal. So let's just watch this stupid program that contains a string and a pointer to a string. I have a string char star and then I have a pointer to that string that I call ptr. As you can see this is a double pointer right? Because the string is itself a pointer. So ptr is a pointer to a pointer. So when you declare a pointer to a pointer you have this structure. This is a visual representation of what is going on. As
you can see pointer is an eight byte in my system object that is pointing to another eight byte object. str as well is pointing to the first char of the string alone. So if you watch carefully you can see that inside these two pointers you have the address right? Inside the str you have the address of the letter h inside my string and inside my ptr I have the address of str right? You can clearly see thanks to the color and thanks to the hexadecimal notation. Indeed we use hexadecimals because they are very cozy when
we want to very clearly see groups of knee bolts. So at the end of the day that's what it is. You can have a pointer to a pointer to a char. Easy right? Of course let me repeat to you. You can create arbitrary complex objects. A very deep multi-level pointer. Of course this is not recommended because more than three level pointers I would say the code starts to become unreadable and this is super bad. Especially for you that you have to read again the code that you wrote. You won't be able to understand your own
code. Now let's watch an example which is crystal clear because we use on a daily basis. So these inputs are argument counter and argument vector. You have seen many many times right? You can also have environment variables but now it's not the point. So I have this argument vector which is a pointer to a pointer to a char. So that's perfect. We can analyze this idea of pointers to pointers thanks to this argument vector. So we are not going to talk about something which is poorly theoretical. We are going to talk about something which is
pretty useful on a daily basis. So here my friend I have a little algorithm that is able to print all the chars inside argument vector and this is a very good representation of what argument vector is. As you can see you have a pointer argument vector that is pointing to a series of pointers that here I have depicted as an array of pointers. Later I will explain better this idea. In my algorithm I have two iterators which are j and k the classic names and then I simply do a nested while loop. With this nested
while loop I'm able to print all the chars. Of course I want to print char by char because I want to make extremely clear how the structure work. So let's start the loop I want to make with you very visually. We have initially j which is equal to zero so I have argv that is pointing to the first pointer with the one in position zero. After that I have a k is equal to zero. This is a refresh operation. I want k every time to be zero so this is going to be the first char
of my string. Then I simply do a loop while argv j k print that char. So initially I'm going to print the point. Of course here I don't use is different from zero because this is redundant. I'm going to use directly the backslash zero as a boolean value. So this is the inner while loop that is working. So I'm going to do while argv j in position k. I'm going to loop. I go in the first position of the string then I go in the second then in the third and so forth right until I
reach the final backslash zero. This final backslash zero is going to act as a boolean value. You know that in c zero is equal to false. So this while loop is going to break. I have a put s so this is going to print a new line. I go in the following line and then I just increase j. So I go in the following pointer boom. You see k is going to be refreshed and then I do again the same thing. You see I loop inside all the chars until I stumble upon the backslash zero.
Here the condition is going to become false. So again the while loop is going to break. I print a new line and then boom I go in j equal two. You see and then I do the same thing. I'm going to print all the string until at the end I'm going to stumble upon this null. This null my friend is a sentinel value. So thanks to this null I can say okay I don't have any more strings in my argument vector. So here my friend we have the actual program that you just saw. So here
for example I have my program that I'm going to launch. For example I don't know I'm going to have a dot out and some inputs. For example hello there okay. These are going to be the strings that are going to be written. So let's try to compile and launch. I'm going to launch with an input for example hello there how are you boom. And as you can see I have in my standard output all these strings. You see cool. So this is a little algorithm that is gonna loop inside the strings pointed by argv. Here
of course I use the square bracket notation because it is more intuitive. Do you agree? But you know that this is just at the end of the day syntactic sugar. I can do also like that. Let's try. I can do at argv plus j right and here I can do. These are more complicated but it's gonna be at argv plus j and then I'm gonna do at argv plus j is gonna be the reference plus k like that. Cool let's start to launch again the code and let's watch if it's gonna work again. As you
can see it works exactly the same because I did exactly the same thing. I'm sure you agree that the square bracket notation is far far better far far more intelligible than this one right. So it's far better to use this and the square bracket notation if you watch carefully really recalls the cell right inside our vector our array. We have the first cell the first level and then the second level which is the chart itself. You see it's fairly intelligible to see what is going on if you have a clear diagram in your mind. So
with the first j I'm selecting the string with the k I'm selecting the chart itself. Now here I have the pro version of the program. Basically I say while at argument vector and while at AV right. You remember from the previous video when I told you that when I double the reference this I get the chart because I can see in its signature right. I have if I double the reference this object here I get the chart. If I single the reference this object what do I get? Well I get a pointer to a chart.
So here is written while I have a pointer to a chart and here I just omitted this. It's different from null right and while V is different from zero you print all the stuff. Here I used the pointer notation to your reference you see how cryptic it is. It's far better to use the square bracket notation when we have multi-level pointers. Anyway let's try to launch this and see if it works and as you can see it works exactly like the previous one. Cool now my friend I'm sure you many times have seen this signature
in the main right. So you can have a star argv array or you can have a double star argv. What is the difference here? Now given that you understand this array pointer duality from the previous video I'm sure you now understand what is going on. Well my friend the problem is always the same array the k. So here my friend I have a little program that is creating a fake main you see that is taking an argument vector and here I'm creating in my main function an argument vector. So this is what thanks to the
right left rule right we start from the identifier then if we can we go right and then we go left array of pointers to char. So this is the initialization of the array I'm going to insert directly the strings right given that this is an array of strings. At the end I'm going to put an all then what do I do I call the fake main function and I pass AV but you remember right that arrays have this peculiarity that are going to decay to a pointer to the first element. So we don't really care
if the objects inside the array are pointers themselves they could have been integers floats doesn't really matter the thing is that we have an array so you know that when we pass an array the function fake main is going to receive a pointer a pointer to what a pointer to a pointer because this array contains pointers that is what is going on let's try this code and let's convince ourselves that this is true. So I'm going to run the code and as you can see I have the inputs that I've given inside my array. Now
we have to understand a point here I want to do this operation creating AV1 which is a pointer to a pointer and then I do the same thing as here I initialize this pointer to pointer like here but I cannot do that let's try to compile this code and let's see the mistake. So I'm going to compile as you can see I cannot do these operation incompatible pointer types initializing char star star with an expression of type char five. The thing is that at this level this is an array of pointers an array so we
have an allocation of space in this case we have an allocation of one two three four five pointers so 40 bytes and as I told you this is an array. Array and pointers are not the same thing they behave similarly in certain conditions but they are not the same. Here I just have a pointer so what what am I doing here I'm assigning this thing to this object this is not this is not possible this is not feasible this makes no sense I can do that here I can do this is equal to AV this
is legal and why it is legal well because you know that the identifier of an array is going to decay to a pointer to the first element so here I have a pointer to a pointer okay that's all there is I think you understand super well what is going on so here of course I can write like this or like that it's exactly the same here when we write in the called function an array it is just to stress out that in the coley function we have an array we are manipulating an array and what
we are using in the called function is just a pointer so let's run again as you can see it works perfectly okay I think you understand what is going on right so my humble suggestion is always the same just keep in mind that an array probably in your code is just a pointer if something bad happens just remember that array are arrays so probably it is an exception the array hasn't decayed so if you want to train a little bit with these pointers to pointers you can come here institute user and do some code for
example you can do ptr either int an equal 42 double ptr triple ptr okay so you do many level pointers you see let's do till four ptr okay so we do yeah although it's the same name ptr one two and three and then I say ptr is equal to the address of n ptr one is equal to the address of ptr you see ptr two is equal to the address of ptr one ptr three is equal to the address of ptr two okay let's try to launch this code and here you see you have a
beautiful representation you have the number and you have ptr here that is pointing to four two then what do you have you have ptr one that is pointing to here I made a mistake say three I won't like that okay let's launch again so I have 42 I have a pointer to 42 then I have pointer one that is pointing to ptr then I have pointer two that is pointing to ptr one and then I have pointer three that is pointing to ptr 2 you see That's the structure that you can expect. Here we can
also see the memory addresses, so we can have a better understanding of what is going on. And you can create all the mess that you want with these pointers, okay? I highly suggest you to come to this website and work out a little bit these ideas. Okay, that's all there is with these double pointers. I don't want to dig too down because this can become very complex. So, my friend, here we are in the last chapter of this mini course. We are going to talk about pointer to functions. I'm not going to go very deep.
First of all, because this starts to be a little complicated. And generally speaking, pointer to functions are not super used. Of course, this boils down to the type of code you're writing. But I would say initially, if you are a beginner and if you are watching these kind of videos, you are not going to really use pointer to functions. Anyway, this is just a gentle introduction. I would like you to understand what is going on with pointer to functions and take home the overall ideas, the overall principles. So, you know that a function is a
symbol that you can call to get some action, right? To perform a specific task. For example, the printf. When you call the printf, you would like to see on the terminal something written. And indeed, that's what it is. But functions can be also pointers. So, let's read together this line. The only things you can do with a function is to call it or to take its address. So, if the name is not followed by the brace, the round brace, well, it means that I don't want the function to be called, but I want something different.
Indeed, I want the address of the function. So, take a message. If you write the name of the function without the round brace, it means that you want the address. It is kind of similar if you want to the array decay, right? When you use the name of the array, it's going to decay to the pointer to the first element. Well, in this case, it's similar if you want. The principle is similar. If you use the name of the function, it's going to quote-unquote decay into the address to the function. So, let's understand better this
idea. Here, as you can see, I have my process memory layout. So, when you launch your program, you know that the operating system is giving you a space in memory, which has this shape. Now, where in my process memory layout, the function foo, in this case, a stupid function that is returning me the value 42 lives? In the stack, in the heap, where is this function? Well, it turns out that the function lives in the text area. Indeed, the text, also called the code segment, contains the instructions of your program. Instructions are bare-bone binaries that
are telling to the computer to do something. You don't have to go too deep into this stuff, or better if you want. That would be marvelous, but it's low, low-level stuff. So, the binary of the function foo, the instruction, lives in this section in my process memory layout. So, if you watch carefully, the printf is trying to print a pointer, you see, from the identifier P. Foo is a function, but I'm not using with the round brace. So, you know, from before, that this is the address of the function foo. So, this is the program,
my friend, and now we're going to launch. So, I'm going to compile, and now I'm going to launch, okay? Here it comes. I have this address, which is kind of peculiar, right? It's very different from the kind of address that we get from a variable. Let me explain it better. If here I do int and b and b equal 13, and I print the two addresses, okay? So, b and p, there is foo, and here I use the address of nb. So, I'm going to compile and launch. You see, they are very different, very, very
different. One is this quote-unquote little number. On the contrary, our variable lives here. Now, I want to explain you something. To do that, I use the sleep function. So, I include uni std. And here I do a sleep 100, okay? So, I want my process to not die, to stay alive, because I want to check this process memory layout. So, I'm going to compile and then just launch with the number cent. So, I have the terminal. I can use the terminal, even though the process is running on the background. So, enter. Now, I check ps.
Here, immediately, you can see the addresses, right? The printf has worked. So, I say ps, you can see that the process is running. Now, I do virtual memory map of the process, and then I pipe this into less. This is just a way to see better what is inside the output of the command, virtual memory map. Okay, enter. Now, with this command, I can see the virtual memory layout, which is this one. Now, we have this address, which is this one, 105.30.6f20. Let's see if we can find here. 105.30.36f. So, here we go from 30
to 130.70. So, you see that the address of a function is here, is in this range. And if you watch carefully, this is the text range. Here, it is an unwritable region. Of course, we cannot change the values inside the text. Here, on the contrary, we have this value, 7ff. I already know that this is the stack, of course. So, let's search for the stack. As you can see here, maybe it is in this range here. 7fea0, 7fea8. So, here I have a8, fd00, fc92c. You see? So, I have the variable which lives in this
range. So, I have one range here. On the contrary, I have the function that is living in this range. Okay? So, this is low-level stuff, technical stuff, but just to give you a glimpse. So, this is the abstract view that I proposed to you, in which we have our function that lives inside this place. Now, let's zoom in a little bit. So, we have our function foo that lives inside the text segment. And basically, this foo is a pointer to instruction 0, if you want, of the function foo. So, instruction 0 of the function foo
has that specific address inside the text segment, you see? To make it clear, if we have more functions, for example, add, subtract, multiply, divide, we are going to have multiple pointers that are going to point to the instruction 0 of the relative function, you see? That's what it is, my friend. That's what is going on. Now, my friend, I have a little program to understand this concept of pointer to functions, okay? Given the principle, given the knowledge that you now have, we are going to understand this program. So, here, immediately, you can see that I
have four prototypes, right? Four functions, add, subtract, multiply, and divide. You immediately notice that they all have the same signature, they all have the same prototype, because they give me back an integer, and they take, as an input, an integer. Okay? Here, I have another function that is called performed arithmetic. And this is a special function because it's going to exploit function pointers. So, the function is taking two integers and this strange thing here, you see? This, my friend, is a pointer to a function. It has this way of writing. Essentially, it is almost like
a prototype. The only difference is that we have these round braces here. And indeed, when it comes to pointer to functions, the syntax is not very simple. So, now, look there in my main. I have a declaration of two variables, a and b. And here, I want to perform arithmetic. In this case, I want to perform the addition. So, I call the function, perform arithmetic, and I give, as an input, add. Add is the name of a function, right? Is the name of this function here. I've defined add here. This is a very silly function
that is returning a plus b. But here, I'm able to pass add. Now, you know it's just the address of the instructions in the text. So, it's just an address to the function. So, here, every time, I call perform arithmetic, but with a different pointer, right? Every time, I change the pointer to the function. Perform arithmetic only knows that it's going to receive a function with a specific signature. And that's the important part. The thing is that a pointer has to be consistent if you want to use in this fashion. All the pointers have to
be an integer as a return and two integers as an input. So, let's try to launch this code and let's watch if it works. So, I'm going to compile. So, here, I have the sum, which is 10 plus 5 is equal to 15, of course. And the address of the sum function is this one. I have the difference. I get 5. This is the address of the difference. As you can see, it's fairly close, right? It's fairly close one each other. We have only a gap of 20 in hexadecimal, of course. So, it's going to
be a gap of 32 bytes. Here, I have the multiplication, and here, I have the quotient. So, the division. Okay, it works. So, at the end of the day, the name of a function is just an address, a spot in your memory in which you have the relative instructions to achieve the goal of the function itself, right? Here, with pointer to functions, we are exploiting this property. I repeat to you that it is important that the signature is the same. Now, let's try to change a little bit. I'm going to do another integer. And this
time, immediately, I get a warning. And it's telling me incompatible function pointer because you can see that this is another type, right? I have another type of pointer. So, it's giving me a warning because I have a pointer to function that has another signature respective to the one that the function is able to take. The last thing we have to demystify is a little bit the syntax which is kind of super tricky of pointers to function. So, we go in this website which is gibberish to see, and we're going to type, for example, int, and
then we're going to do, I don't know, pointer to foo, and then we're going to do int int. We have declare foo as a pointer to function that takes two integers and return an integer. Now, why it is important to use the round braces here? Well, let's try to remove them. This is a prototype, as you can see. We're declaring foo as a function that is returning a pointer to an integer. So, that's the problem. On the contrary, if I say, hey, you have to read this together, I'm saying, oh, I see. So, this foo
is a pointer to a function. Get it? Of course, here I have all the values that I can have, and that's all there is. This is the way by which you can declare a pointer to a function. I want to stress out that I don't use pointers to function very often, but it's useful to know how they work. So, I just gave you the overall principle, the overall idea. A function name is just a fancy way of talking about an address in memory. I recall to you that all the names, all the variable names, function
names are just an abstraction for us to read the code. But at the end of the day, we have only numbers. We have only addresses. We have only binary values. All the symbols are just an abstraction for us. Okay, my friend, I think that with this mini-course, you have a better understanding of pointers. Hopefully, I really hope. Basically, this is the video I wanted to watch when I started to learn about the C programming language. Of course, there is a lot more to dig, to understand, to practice, because pointers are a tremendous bad beast to
tame, but this, I think, is a very good introduction. All right, my friend, thanks for watching. Take care.