 Welcome back to 105. Thank you for joining me today. So today we get to start the first hard topic of C and what makes CC. So we get to talk today about pointers. So pointers give you a lot of control. They're also the source of a lot of errors. So it's kind of like walking a tightrope over a pit of snakes a little bit. So I'm here to help you not fall into the pit of snakes. So nothing bad happens to you. So first we'll do a bit of a rewind all the way back to lecture two. So remember computers, they just store numbers. They store binary numbers more specifically like a light switch either zero, it's off or one, it's on. So if we assume we have like a 64 bit number which is eight bytes. So always remember that there are eight bits in a byte. So if we represent like as a whole number and then we want to compute what it is. So remember before we'd like in base 10 we had like the ones column, the tens column, the hundreds columns, the thousands column, do do do do. We changed the base two and they just became like the ones column, the twos column, the fours column all the way up to if we have a 64 bit number all the way up to the two to the 63 which is some gigantic number. So if we just have the binary number one, zero, one what decimal number is one, zero, one in binary if we remember? I saw this five, yeah five. So it's still under my rule. I can count up to five that's fine. So this is like one, one, zero twos, one, four and then zeros for the rest of them. So this is the decimal number five. So especially if we have 64 bits turns out you don't really want to write 64 bits. It's a bit of a pain to write out and especially read. So if you were trying to see if like two numbers are different and you got 64 ones and zeros and I said which are they the same number or not and they looked almost identical except for one you'd probably have some difficulties with it. So decimal numbers for humans, computers like powers of two, they are in binary but is there something else we can actually use that might be a bit more useful for us trying to understand computers. So computers typically a very useful number system is actually base 16 instead of two or 10 seems to be more convenient. So the rules for like base 16 would work exactly the same. So for decimal we have 10 options for a digit so zero all the way up to nine. For binary that's two, we only have two options, zero or one. For base 16 it means we need 16 options. So obvious ones would be zero through nine but what about the six other characters? How would I represent 10, 11, 12, 13, 14, 15? Well turns out we can just borrow some letters. So we call the base 16 number system, we call it hexadecimal and we just borrow letters to represent the values 10 through 15. Otherwise aside from this being looking a bit weird behaves exactly the same as any other number system. So just goes in order. So 10 is A, 11 is B, 12 is C, 13 is D, 14 is E, 15 is F alphabetical. So at least we should know our ABCs up to F. So don't need to know anything more than that. So we called like a single digit in binary a bit. So in hexadecimal that digit which would be zero to nine or A to F could call it a hex it like a hex digit. No one actually does that. So they just call the whole number a hex number. So in C a hex number, so short for hexadecimal number you'll see it start with zero X and then anything after that will be in hex. So it will be zero to nine or A to F and just like any normal numbers we could just have like F F F one, something like that. So this is not going to testable but it will help you understand some output that happens. So like nothing in the exam will be like, hey, here's a hex number, what is it in decimal? This is only for you to just get a glimpse as to this because we will see it today when we talk about pointers. But other than that, same rules apply. We just have a different base now. Turns out why it's convenient is because each hex digit actually represents four bits in a row. So if we have four bits, well we can represent two to the four different things or 16 things, turns out that's exactly what a single hex digit will represent. So one hex digit represents four bits, kind of convenient, also why it works well is because computers deal with bytes so that is an eight bit number, so eight bits to a byte. So two hex characters actually represents a byte. So since they're four each, if we have two of them, we got eight, that's one byte. So now if we had a giant 64 bit number we could represent it in hex and since each hex character represents four bits, well I only need 16 hex characters instead of 64 which is a bit better. So let's say everything else is zero and I just have the hex number f4. So what decimal number would be f4? So all the same rules would apply. So if this was base 10, this would be like the 10s times whatever the value is of this digit. Well for hex, same idea, it's 16 times whatever the value is of f. Anyone already remember what the value of f is? 15. So it's 16 times 15, so that's the value of this which is 240 I believe and then plus, well it's one times four. So in decimal this would be the number 244 which thankfully I got correct but same number system, same ideas. We just have base 16 instead of base 10 or base two. So if you hear anyone say things in hex, computers like hex you'll mostly see any like low level details about a computer in hex. So also remember about memory. So in memory each address contains a byte and here I have the same figure I had in lecture two. So the value is in blue. If I just represent each byte as a whole number, well it can be any value from zero to 255. So here I have eight bytes with different values and below them I have the location they are in memory. So this one's at 512,000. Then they just go up incrementally. So one, two, do, do, do. Why 512,000? I just made up a number. So there's no particular reason why I picked that just every bit lives at an address and they're just numbers. So what I could do is and what actually you'll see as the result of that C will give you, we will be accessing these addresses today and reading them and looking at them. So it turns out whenever C shows you an address it shows it to you in hex. So this exact same thing I could write like this. So instead of 512,000 that's in decimal I could write it out in hex. Turns out it's 7D000, that's enough zeros. So if you don't believe me you can go try it out because this would be 16 to the power of zero, 16 to the power of one, 16 to the power of two, 16 to the power of three, and then 16 to the power of four. So you can do that math and figure out that it is actually the exact same number. So questions about that. So when we see addresses, same idea each byte will live at some address starting at zero going up to some gigantic number. Turns out when we actually get to read them today they'll be in hex like this but same things just apply just a number. Everyone all good? More or less? All right, perfect. So remember we also said that C will go ahead and pick a location in memory for you. So if you create a variable called like int x that'll be somewhere in memory. So an int takes up four bytes so it might take up these four bytes down here. So it might take up, so each address is the address of a byte. So one, two, three, four and they'll all be in order like that. So a pointer is just the starting address of a value in memory. So when we did that ampersand operator that is the address of and that gives us the address of the first byte of that value. So for values that take up multiple bytes like int x it's always the lowest address. So it's always the first starting one. So in the previous example, if we did address of x it would be this value. It would be 7d 0004. So pointers are actually a new type. We get a new type in this course. So if we have something like int x equals one we can't do something like int z equals the address of x. So it turns out that the type of the address of x is actually x star. And how you read this is, well we call this a pointer. So we say it's a pointer to an integer value. And a pointer is just going to be where in memory that variable actually lives. So each time we take the address of a variable well we add an asterisk to its type. So if we have int x equals one and we can do something like int star and for the star here we could put a space before it or after it. It's kind of up to taste. C programmers like to put it right beside the variable name. So they'll do int star. Z equals ampersand of x and you're allowed to do that. So now z is a pointer to x we would say but really it's just storing the address that where the value of x is stored. So we could do the address of z in this case. So then we would get the address of the address and it would be int star star which would be a pointer to a pointer to an integer value which is very confusing. Don't worry this lecture is mostly set up so that we get some exposure to it. We can ask questions, we can go through lots of examples later. So you can only take the address of a variable because variables by the C standard need to at least appear to be stored in memory and a value by itself. So if I just write the value of like one plus one or one plus two those ints one and two might never actually be stored in memory. Only variables are stored in memory. So that means if I try something silly like I take the address of four while you get a very, very unhelpful message and this is an especially bad compiler or a compiler message. So we could have int x actually let's just say int star z equals the address of four. Now there's a red line there if I compile it I get something very incomprehensible. It says cannot take the address of an R value of type int. What in the hell does that mean? I have worked on compilers and I can tell you even to me that message sucks. So that message should probably actually say you cannot take the address of a literal you can't take the address of four something like that that would probably be more helpful. Could it mean that like so basically could a four even if the same one like that? I kind of get what the, yeah the error message seems to think that if it's an int we can't take the address of it but I mean that's kind of silly because if we have this the R value is an int I can take the address of it. Yeah okay. So yeah it turns out it's just a bad error message. It's terrible. But so if you ever see some message you can't comprehend it's because you tried to take the address of like just a random value. So don't do that. Again this error message is beyond terrible. And then question should we put six digits for hex numbers like six zeros if the number is a zero I'm not sure what that means. So hex numbers are just like any other numbers. If we have like a set size they have to be they get leading zeros like anything else. All right. So in order to use a pointer and get the value it's pointing to back we can use a star operator which is a unary operator to access the value of the address. So if we have int x equals one and int star z equals the address of x that means while z currently has the address of x stored in it and we can do star z and star we'll go ahead and get the value at that memory location for us. So star z will take the address that's currently at z and then read the value that is currently pointing to. So in this case we'll have a better worked example coming up but since x is equal to one the z would be pointing to that value one and then we can go ahead and read the value that z is currently pointing to which in this case is one. So after the statement y equals star z we would have y equal to one. So you can also kind of reason about that each time you use the star operator it removes a star from the type. So if I have the variable like int star star z or we have a pointer to a pointer to an integer if I have just an expression star z the resulting type of that is just a pointer to an integer. So each time you de-reference it which I probably shouldn't say because I haven't sent de-reference yet you just remove a star from the pointer type. So here's a more worked example just so it makes it a bit more clear. So we can actually also change the values of variables through pointers. So let's start off with this little example. So we have int main void. So when we start running it we start running it at main. So our first line is just int x equals one. So we're declaring a variable called x and assigning it to one. So in this bottom here I will just have all the variables that live within main that are currently in scope like we had yesterday. So in main after that line I have an x and then also in the blue square is going to be the value of that variable. So now I have x equal to one. Everyone good? All right, so then I declare a y, set it equal to two. So after that line I have a y and its value is two. Now I have int star z so this will just declare a variable called z which is a pointer to an integer and it is going to be the value, initial value is the address of x. So this is why we call it a pointer. So how, we don't really care about the actual value of that address. It'll be like that random 7D004 maybe. Who really cares? We just care what value it's actually currently pointing to and why we call it a pointer. So instead of doing the value we draw this arrow. So this arrow means that the address that is currently stored at z is currently pointing at wherever the value of x is. And you can also kind of think of this as like a remote control if you want. That's one of the ways I learned it. So instead of going ahead and changing x directly we can change x through z because we can access the value of x through z. So now if I have the line here I dereference x so that means I access the value that z is pointing to and I change it to three. So in this case each time we do the star which is called dereferencing so accessing the value at I would take z, see what address it is pointing to and then I know this is the value I'm modifying because well I have to modify an integer and this value is currently an integer. So if I do that I'm immediately changing x. So I change the value of x if I wanted to go ahead and print x now I would see that I printed three. So any questions about that one? Or fun things we wanna do with that? Pointers are real fun. Yeah. Yes, yes, yeah so well not the same number is that the same number of the value at z? Yeah. So this dereferencing so that's just accessing the value that that variable is pointing to. So if I have a dereference of z here it means while I'm not using z I'm using z to figure out what I need to change. So in this case I need to change x because it's pointing to x. Yeah, you're allowed to do it for the left side because you're essentially saying well this is the thing I want to change and it's basically a variable. Yeah, yeah all the pointers so everyone has like you've probably heard I have a 64 bit CPU everyone's probably heard that before means your pointers are 64 bits. So that's how big they are. So, yeah so what's the application of it? Let's go here. So let's just make sure that we can go ahead and change it. So let's have that example written out what I do. I said z equals three. So now if I want to show that I actually changed x through z or z, I forgot what country I'm in. I can go ahead and print off the value of x. Oops, I can type compile too. So in this case I see that x is indeed three so I'm changing it through z. And then yeah some other questions can I initialize a pointer without using an ampersand like z equals five? So if you do that, that means you're just randomly pointing at address five. I think c will probably let you do that without complaints and which is why I talked about the pit of snakes. So this is an instance of the pit of snakes. So c will gladly let me do something like that. It gives me a little bit of a warning saying that I'm converting to a pointer. So I'm converting an int to an int star but happily lets me do that and I will blow off my leg by getting a segmentation fault. So this is probably the first time you've ever seen that. This will be very common. So you basically see a segmentation fault every time you access memory you shouldn't be accessing. So in this case I picked the random address five and said please access the integer at location five and turned out that that was not valid. And the error you get when you run it is called a segmentation fault which if you want to know how that works you will figure that out once you take 355 in the operating system course. So for now you should always just take the address of a variable and then you're guaranteed that the memory address you get is actually valid. And yeah, it will be represented as some gigantic hex message. And yeah, why is it useful? Well, we had to use it for scan F, right? Because with scan F we did something like we defined an X and then we did like scan F like this, right? Whoops, scan F. Yeah, we did something like this, right? Because after scan F ran it changed the value of our X. So how did it do it? Well, the only way it could do it is through pointer because remember function calls they passed by value so if I just did something like X it gets a copy of one and won't change anything. So we had to give it the address of X to tell it what it can modify. So it can go ahead and modify our value of X for us and we can do that through a pointer. So now hopefully the mystery of why we had to do the ampersand for scan F is fixed. And here again, if I just randomly said Z is three that'll blow up too. Like basically you're gonna have to guess a valid address which is fairly hard. So and we'll actually print off some addresses in a bit to see the ballpark of what they're in. Other question. So without the asterisks I'm not sure which one you want me to delete. Like here, like Z equals three. If we do Z equals three, well Z nothing's going to happen. So we create a variable called Z which is a pointer to an int. We get the actual address of X. Who knows what it is. And then we just say okay the address is now three. Nothing bad happens in this case. We're just changing what address Z is. But if I did something like this, that's bad because ampersand Z would try to access the value at that memory location. So whatever the integer that starts at memory address three is and then it just dies. So I can go ahead and run this if I want and it's segmentation fault. Yeah, Z is straight up another type. It's star, so it's a pointer to an integer. And again, if I tried to make like Z an integer and compile it, C actually lets me do such a thing but it gives me some warning that like, hey you're trying to convert a pointer to an int. I did that for you but you probably didn't mean it. And in this case, well, X is gonna be just fine even if I do like change Z because now I just made Z some random value and then reassigned it to another one. If I want, I can print Z if I want the output would look weird. So here I can print Z. It tries to do some conversion so it tries to take some large hex number basically and then try to represent it as an integer and it gets, you get some random crap. Yeah, so if you know, if you really wanted to you could get to Y somehow by knowing in what order variables get set out in memory. So you could, so it depends on the CPU some data structure that we don't know yet and you're not guaranteed it'll always be the same. So we'll see once we start talking about another data structure basically how to do that. Yeah, so just changing Z, whoops, here. Just changing Z to just some random value that's gonna change the address that Z currently is pointing to. And if I just change it to a random address trying to use it, probably not gonna work. So if I do this, then currently the address that's stored at Z is the start of variable X and if I dereference it, that is going to be the value that X is currently pointing at. If I want to point to something different, I could point at Y and now Y will change from A2 to a three, I can print out three. Yeah, so there are some things you can do. Ill-advised, not guaranteed to always work across all CPUs, yeah. Yep, yeah, if you create a global variable, so in, I'll call it G, you can also get the address of G if you want, that's fine. Yeah, at what? That's the entire computer's memory, right? So could you like actually mess up? Oh yeah, so that's a good question that is a question I ironically just got asked today in the operating system course. So yeah, since I can access memory, does that mean that hey, I can just guess a random memory and then I can read my bank account information or something like that? The answer to that is no, your operating system is very smart and very secure, so you can't do that through mechanisms, I will not even begin to explain now, but if you wanna know, hey, you can look at the operating system course I'm teaching if you want, but it looks like you can access all of memory, that's just an illusion. Yeah, so any behavior you see with just playing with pointers, not guaranteed to always work and that's why you can easily blow off your legs with a shotgun. So let's step through another smaller example. Here, we'll add a function call in the mix, this probably looks a lot more like scanf. So I'm writing a function called set three, takes, well in this case I'll say a variable called p, which is a pointer to an integer and what it does is it tries to access the value that p is currently pointing to and then set it equal to three. So in my main, I'm gonna start off again by declaring a variable called x, setting it equal to one. So in main, x would be in scope or valid to use and we can go ahead and that's it, just set to one. Then we have y, we create it, set it to the value to two. Then we have z, so that's the address of x. So it will point to the location that x is in memory. Again, all my blue boxes here are bytes that are currently in memory. Now I'm going to do a function call to set three. So remember, c is call by value. So this set three function is going to get a copy of z and it's going to copy it to the argument p. So we'll start a new scope. It will be called, well in the set three function and we get p, which is a copy of z in this case and it's also an address and it points to the same thing. So the address has the same value. Remember an address is just the starting location of in this case an integer. So we would copy it and it would end up pointing to the exact same thing. So this is a way to get around that scope rule because remember in set three, well I can't access x, I can't access y. Normally can't access it in a different function. But through a pointer, I can access it. So here I can now access x in the function set three. Now after this line runs, well it's going since p is pointing at this location of memory which just has the value one, it can go ahead and change the value from a one to a three and then that function is done. So there's an implicit return at the end of it and we'd resume from where we left off in main then hit return zero and then our program would be done. And yes, this is the recommended way if we want to go ahead and use variables and try and change them in different functions without actually using a global variable or anything like that. But sometimes if you don't have to you don't really want to use a pointer because while some find it a bit weird that the value of x just kind of changed here. And yeah, this is same thing as doing my dereference of z in the previous example. I just moved it in the function so we can kind of combine last lecture, review it a bit with this one. So things are still copied by value but now I'm copying an address instead of just a value. But an address turns out it's just a value. So this is why I told you about hexes. So we can actually print the address of a pointer if we want to format specifier. It's a new one. It is percent P, P for pointer and it expects a weird looking type. So void star which knowing what we know so far that's a pointer to nothing. So it turns out void star is basically just C saying like it's a generic pointer. I don't actually care what the value is and you're not allowed to access it. It's just saying it's a pointer. I don't really care what value type it's pointing to. So the rule is, well, you can't dereference it because if we got rid of the star then we would just have void which is nothing. So we can't dereference a null pointer or sorry, not a null pointer, a void pointer. So the other rule is that you're allowed to cast a pointer of any type to void star. So if I have an int star, a pointer to an int I can say, okay, it's a pointer to and I don't give a fill in blank. So how that looks is I can just do the same thing except I can print values. So let's see the exact same thing. So here I'm just going to print the values. So I have an X, I have a Y and then Z is currently pointing to the address of X. So here I say X, so the address is this. So I have to do an explicit cast to make my compiler happy to avoid star. But it's the address of X. So this is a pointer to an int and I just make C happy by saying it's a pointer to I don't care because that's the type that percent P wants. Then I just have equal to whatever the value is. Same for Y. Then I do a set three with Z, which is the address of X and then a set three of the, just straight up the address of Y. And then here I'll print off the same things after I call set three. And then in set three, I'll print off the address of P, which would be a pointer to a pointer to an int. So that's just the address that the argument lives at. And then the value of that pointer, which well, since it's copied by value, it should match Z and it should match the address of Y. And then here I print off the value that, the value at the memory location that P is pointing to by doing the dereference, which in this case, it would be an int. And then I'll go ahead and just do set it equal to three. So if I run that, I should see a bit of consistency here, print. Let's make that bigger. So this is where I showed you the hex things. So these are memory addresses. They're just very large numbers in hex. So don't need to worry about them other than they're consistent. So it turns out that X is at this location. Its value is one. Y is at this location. Its value is two. P is at a different location because well, it should be stored somewhere else in memory. And its value is this. So that makes sense because the first time we called it, it was Z, which was the address of X. So this matches the address of X. And then we just changed, and currently the value was one because we haven't changed it yet. Then after this line, we went ahead and changed it. Then P turns out its address is the same as before. Doesn't necessarily have to be, but in this case, it will be. And then its value is this long ass thing, which turns out to be the address of Y. So these two are the same, which makes sense. And while the value at Y before we modify it is two, makes sense. And then we modify it. The address of X never changed, but its value changed to three. The address of Y never changed, but its value is now three. Whew, well, whirlwind. All right, we're good with that, ish. So don't worry, pointers are hard. So just to color it to see that what is consistent. So red is the address of X. So it's the address of X. It's the first argument we gave to the function. So it was the value of P. And then while the address doesn't change at the very end. And then in light blue, we have the address of Y. So it was the value that we gave P for the function call. And it was still the address of Y afterwards. And then here, I just put the address of P in different colors because in this case, they were the same address, but in general, they don't have to be. So, oops, questions about that. All right, we should be able to now, oh, yeah, yeah. So it turns out that on a specific CPU, these go sequentially, well, sequentially down. So if I made another i, something like that, yeah, I would get the address, same thing, except it would be 30. But it turns out you can't rely on that. That would just be for this particular CPU architecture. All right, so should be able to understand that now and why that actually swaps two values. So let us simulate. So here is our swap function. So we should now understand that, hey, it does indeed swap around A and B. So if we start executing it, we'd execute at main, we'd have this line. So declaring an integer called A and setting its value equal to one. So here, we'll do the same thing in the slides. In scope and main, we'll have A and its value is going to be one. Then we'll have B, it's a new variable, its value is going to be two. Then when we hit this print line, what's the value of A and what's the value of B? Yeah, A should be one, B should be two because, well, that's what we just set it equal to. So hopefully pointers did not break you so far. So up until this point, we haven't used pointers. So before swap, we'd see A is one, B is two. But now we're going to do swap the address of A and the address of B. So since things are copied by value, we're doing a function call to something called swap. And up here, we'll use the argument name. So just to keep track of where I came from, I'll put a one beside this arrow to know I have to go back there after I'm done with this swap. So in the swap function, it will have a variable called A, which is just some address. And it's pointing to, since it's done in order, it's the same as this. So it is pointing to mains A. So it's pointing right here. And then we would also have B, which is an address, and it's pointing to A. And it's pointing electrical whoop. All right, and it's pointing to B there. So we're good with that so far. All right, so next thing we do in swap is create a variable called temp. And then its value is the value at swaps A. So because there's just one star or dereference, if we wanna use that fun term, that is not in the slides, I go ahead, I follow A. Let's take a higher layer. I follow A, it points me to an integer, and then that's the value I want to use. So temp would be equal to one. And then next line, I'm going to be modifying the value at A and then using the value at B. So the value at B, which is here, the value at this B is currently two. So I'm going to change whatever A is pointing to, to two. So it turns out A in swap is pointing to the value one. So I go ahead and I change that from, oops, delete, from A one to a two. And now next line, I'm modifying what swaps B is pointing to, which is this value of two. And I am assigning it the value of temp, which is just simply one. So now B, or this value would switch to one. Now, we're done with this function. So they could have written a return here, but the compiler will implicitly put one there if you didn't add one. So it just returns from where it came from. So we resume back here. So we can go ahead and just remove the one. We know where we came from. Now, when we print main after swap, well, swap is now gone. So I can delete it. So now I just have that. And now when I print, I'm going to get main after swap, almost wrote A is two, B is one. So questions about now how the swap function works. So it actually swapped the two values for us because we used pointers. Didn't have to resort to using globals or anything like that. Yeah, I don't really need to comment much more. So any other questions about that? And the question is from the line. So from this line, does it take the value, this value at the moment of declaration? The answer is no, doesn't have to. But in this case, well, we set A based off the argument and then we take the value at, whatever the value is, it's currently pointing to. So if we go ahead and we called swap again, well, in this case, we'd be pointing at the same things, but I could swap the other order. I could do swap the address of B and then swap the address of A, something like that and just swap it, but swap it in the other order if I want. So same thing will happen, but now A will be accessing mains B and B will be accessing mains A, which might be a bit weird, but again, doesn't really matter about the names of the arguments. They just always get copied. But if I called swap again and just change the order of the arguments, doesn't really matter. And if I call swap two times, well, I swap and then I swap back, I'll get the same result at the end of the day. All right, any other last minute questions? Sweet, if not, I will take up the rest on Discord if there's anything else. So just remember, boom for ya, we're all in this together. Thank you.