[Section 5 - More Comfortable] [Rob Bowden - Harvard University] [This is CS50. - CS50.TV] Like I said in my email, there are a lot of things you can use other than the appliance to actually do the problem sets. We recommend you do it in the appliance just because then we can more easily help you and we know how everything is going to work. But as one example of where you can do things if, say, you don't have access to an appliance or you want to work in the Science Center basement-- which actually they have the appliance too-- if you want to work anywhere. One example is have you seen/heard of SSH? SSH is basically just like connect to something. Actually, right now I'm SSHed into the appliance. I never work directly in the appliance. Here is the appliance, and if you look down here you see this IP address. I never work in the appliance itself; I always come over to an iTerm2 window/terminal window. You can SSH to that IP address, ssh jharvard@192.168.129.128. I remember that number very easily because it's such a nice pattern. But that will ask me for my password, and now I'm in the appliance. Basically, at this point, if you opened up a terminal inside of the appliance itself, this interface, however you would use it, is exactly the same as the interface I'm using over here but now you're SSHed. You don't have to SSH to the appliance. One example of another place you could SSH to is I'm pretty sure you have by default-- Oh. Bigger. All of you should have by default FAS accounts on the FAS servers. For me, I would SSH to rbowden@nice.fas.harvard.edu. It's going to ask you that the first time, and you say yes. My password is just going to be my FAS password. And so now, I'm SSHed to the nice servers, and I can do anything I want on here. A lot of classes you might take, like 124, are going to have you upload stuff to here to actually submit your problem sets. But say you don't have access to your appliance. Then you can do things, like on here it will say-- This is just our section of questions. It will ask you to do this in the appliance. Instead I'll just do it on the server. I'm going to unzip that. The problem is going to be that you're used to using something like gedit or whatever inside of the appliance. You're not going to have that on the FAS server. It's all just going to be this textual interface. So you could either one, try to learn a text editor that they do have. They have Nano. Nano is usually pretty easy to use. You can use your arrows and type normally. So that's not hard. If you want to get really fancy you can use Emacs, which I probably shouldn't have opened because I don't even know how to close Emacs. Control X, Control C? Yeah. Or you can use Vim, which is what I use. And so those are your options. If you don't want to do that, you can also, if you look at manual.cs50.net-- Oh. On a PC, you can SSH using PuTTY, which you're going to have to download separately. On a Mac, you can just by default use Terminal or you can download iTerm2, which is like a nice, fancy Terminal. If you go to manual.cs50.net you'll see a link to Notepad++, which is what you can use on a PC. It lets you SFTP from Notepad++, which is basically SSH. What this will let you do is edit your files locally, and then whenever you want to save them, it will save to nice.fas, where you can then run them. And the equivalent on a Mac is going to be TextWrangler. So it lets you do the same thing. It lets you edit files locally and save them to nice.fas, where you can then run them. So if you're ever stuck without an appliance, you have these options to still do your problem sets. The one problem is going to be that you're not going to have the CS50 library because nice.fas does not by default have that. You can either download the CS50 library-- I don't think I need that at this point. You can either download the CS50 library and copy it over to nice.fas, or I think at this point we don't use it anymore anyway. Or if we do, you can for the time being replace it with the implementations of the functions in the CS50 library anyway. So that shouldn't be that much of a restriction. And that's that. I'll go back to the appliance now; we'll do everything in the appliance. Looking at our section of questions, at the beginning, like I said in my email, we have to talk about the one short you were supposed to watch. We have the Redirecting & Pipes and these three questions. To which stream do functions like printf write by default? So stream. What is a stream? A stream is basically like it's just some-- It's not even a source of 1s and 0s. The stream it's asking for here is standard out. And so standard out is a stream that when you write to it, it appears on the screen. Standard out, by stream, it means you just write 1s and 0s to it, and the other end of standard out just reads from that stream. It's just a string of 1s and 0s. You can write to streams or you can read from streams depending on what the stream actually is. The other two default streams are standard in and standard error. Standard in is whenever you do GetString, it's waiting for you to input stuff. So it waiting for you, it's actually waiting on standard in, which is really what you get when you type at the keyboard. You're typing into standard in. Standard error is basically equivalent to standard out, but it's specialized in that when you print to standard error, you're supposed to only print error messages to that so you can differentiate between regular messages printed to the screen versus error messages depending on whether they went to standard out or standard error. Files too. Standard out, standard in, and standard error are just special streams, but really any file, when you open a file, it becomes a stream of bytes where you can just read from that stream. You, for the most part, can just think of a file as a stream of bytes. So what streams do they write to by default? Standard out. What's the difference between > and >>? Did anyone watch the video beforehand? Okay. > is going to be how you redirect into files, and >> is also going to redirect output into files, but it's instead going to append to the file. For example, let's say I happen to have dict right here, and the only stuff inside of dict is cat, cat, dog, fish, dog. One command that you have at the command line is cat, which is just going to print what's in a file. So when I say cat dict, it's going to print cat, cat, dog, fish, dog. That's all cat does. That means that it printed to standard out cat, cat, dog, fish, dog. If I instead want to redirect that to a file, I can use > and redirect it to whatever the file is. I'll call the file file. So now if I ls, I'll see I have a new file called file. And if I open it up, it's going to have exactly what cat put at the command line. So now if I do that again, then it's going to redirect the output into file, and I'm going to have the same exact thing. So technically, it completely overrode what we had. And we'll see if I change dict, I took out dog. Now if we cat dict into file again, we're going to have the new version with dog removed. So it completely overrides it. Instead, if we use >>, it's going to append file. Now, opening file, we see we have just the same thing printed twice because it was there once, then we appended to the original. So that's what > and >> do. Does the next one ask-- It does not ask about it. The other one that we have is <, which if > redirects standard out, < is going to be redirect standard in. Let's see if we have an example. I can write one real quick. Let's take any file, hello.c. Relatively straightforward file. I'm just getting a string and then printing "Hello" whatever the string I just entered was. So make hello and then ./hello. Now it's prompting me to enter something, which means it's waiting on things to be entered into standard in. So enter whatever I want into standard in. We're just going to say Hello, Rob! Then it's printing to standard out Hello, Rob! If I do ./hello and then redirect, for now you can only redirect from a file. So if I put in some file, txt, and I put Rob, if I run hello and then redirect the file txt into ./hello, it's going to say Hello, Rob! immediately. When it first gets to GetString and it's waiting on standard in, standard in is no longer waiting on the keyboard for data to get entered. Instead, we have redirected standard in to read from the file txt. And so it's going to read from the file txt, which is just the line Rob, and then it's going to print Hello, Rob! And if I wanted, I could also do ./hello < txt and then the standard out that it's printing, which is Hello, Rob!, I can redirect that into its own file. I'll just call the file hello--no, I won't, because that's the executable--txt2. Now, txt2 is going to have the output of ./hello < txt, which is going to be Hello, Rob! Questions? Okay. So then here we have pipeline. Pipes are the last unit of redirection. Oh. I guess one more unit of redirection is if instead of > you do 2>, that's redirecting standard error. So if something went to standard error, it would not get put into txt2. But notice if I do 2>, then it's still printing Hello, Rob! to the command line because I'm only redirecting standard error, I'm not redirecting standard out. Standard error and standard out are different. If you wanted to actually write to standard error, then I could change this to be fprintf to stderr. So printf, by default, prints to standard out. If I want to print to standard error manually, then I have to use fprintf and specify what I want to print to. If instead I did fprintf stdout, then that's basically equivalent to printf. But fprintf to standard error. So now, if I redirect this into txt2, Hello, Rob! is still getting printed at the command line since it's getting printed to standard error and I'm only redirecting standard out. If I now redirect standard error, now it didn't get printed, and txt2 is going to be Hello, Rob! So now, you can print your actual errors to standard error and print your regular messages to standard out. And so when you run your program, you can run it as ./hello this type with the 2> so that your program is going to run normally, but any error messages that you get you can check later in your error log, so errors, and then look later and your errors file will have any errors that happened. Questions? The last one is pipe, which you can think of as taking the standard out from one command and making it the standard in of the next command. An example here is echo is a command line thing that is just going to echo whatever I put as its argument. I won't put quotes. Echo blah, blah, blah is just going to print blah, blah, blah. Before, when I said I had to put Rob into a txt file because I can only redirect txt files, instead,/ if I echo Rob and then pipe it into ./hello, that will also do the same type of thing. This is taking the output of this command, echo Rob, and using it as the input for ./hello. You can think of it as first redirect echo Rob into a file and then input into ./hello that file that was just outputted. But it takes the temporary file out of the picture. Questions on that? The next question is going to involve this. What pipeline could you use to find the number of unique names in a file called names.txt? The commands we're going to want to use here are unique, so uniq, and then wc. You can do man uniq to actually look at what that does, and it's just going to filter adjacent matching lines from the input. And man wc is going to print the newline, word, and byte counts for each file. And the last one we're going to want to use is sort, which is going to just sort lines of txt file. If I make some txt file, names.txt, and it's Rob, Tommy, Joseph, Tommy, Joseph, RJ, Rob, what I want to do here is find the number of unique names in this file. So what should the answer be? >>[student] 4. >>Yeah. It should be 4 since Rob, Tommy, Joseph, RJ are the only unique names in this file. The first step, if I just do word count on names.txt, this is actually telling me everything. This is actually printing--let's see, man wc--newlines, words, and byte count. If I only care about lines, then I can just do wc -l names.txt. So that's step 1. But I don't want to wc -l names.txt because names.txt just contains all the names, and I want to filter out any non-unique ones. So if I do uniq names.txt, that doesn't quite give me what I want because the duplicated names are still there. Why is that? Why is uniq not doing what I want? [student] The duplicates are not [inaudible] >>Yeah. Remember the man page for uniq says filter adjacent matching lines. They're not adjacent, so it won't filter them. If I sort them first, sort names.txt is going to put all the duplicate lines together. So now sort names.txt is that. I'm going to want to use that as the input to uniq, which is | uniq. That gives me Joseph, RJ, Rob, Tommy, and I want to use that as the input to wc -l, which is going to give me 4. Like it says here, what pipeline could you use? You can do a lot of things like using a series of commands where you use the output from one command as the input to the next command. You can do a lot of things, a lot of clever things. Questions? Okay. That's it for pipes and redirection. Now we go on to the actual stuff, the coding stuff. Inside of this PDF, you'll see this command, and you'll want to run this command in your appliance. wget is the command for just getting something from the Internet, basically, so wget and this URL. If you went to this URL in your browser, it would download that file. I just clicked on it, so it downloaded the file for me. But writing wget of that thing inside of the terminal is just going to download it into your terminal. I have section5.zip, and you'll want to unzip section5.zip, which is going to give you a folder called section5, which is going to have all of the files we're going to be using today inside of it. As these programs' file names suggest, they're a bit buggy, so your mission is to figure out why using gdb. Does everyone have them downloaded/know how to get them downloaded into their appliance? Okay. Running ./buggy1, it will say Segmentation fault (core dumped), which any time you get a segfault, it's a bad thing. Under what circumstances do you get a segfault? [student] Dereferencing a null pointer. >>Yeah. So that is one example. Dereferencing a null pointer you're going to get a segfault. What a segfault means is you're touching memory you shouldn't be touching. So dereferencing a null pointer is touching address 0, and basically, all computers nowadays say that address 0 is memory you shouldn't be touching. So that's why dereferencing a null pointer results in a segfault. When you happen to not initialize a pointer, then it has a garbage value, and so when you try to dereference it, in all likelihood you're touching memory that's in the middle of nowhere. If you happen to get lucky and the garbage value happened to point to somewhere on the stack or something, then when you dereference that pointer which you haven't initialized, nothing will go wrong. But if it's pointing to, say, somewhere between the stack and the heap, or it's pointing just to somewhere that hasn't been used by your program yet, then you're touching memory you shouldn't be touching and you segfault. When you write a recursive function and it recurses too many times and your stack grows too large and the stack collides into things that it shouldn't be colliding with, you're touching memory you shouldn't be touching, so you segfault. That is what a segfault is. It's also the same reason that if you have a string like-- let's go back to the previous program. In hello.c--I'm just going to make something else. char *s = "hello world!"; If I use *s = something or s[0] = 'X'; so make hello, ./hello, why did that segfault? Why did this segfault? What would you expect to happen? If I did printf("%s\n", s); what would you expect to be printed? [student] X hello. >>Yeah. The problem is that when you declare a string like this, s is a pointer that's going to go on the stack, and what s is pointing to is this string which is contained in read-only memory. So just by the name, read-only memory, you should get the idea that if you try to change what's in read-only memory, you're doing something you shouldn't be doing with memory and you segfault. This is actually a big difference between char *s and char s[]. So char s[], now this string is going to be put on the stack, and the stack is not read-only, which means that this should work perfectly fine. And it does. Remember that when I do char *s = "hello world!", s itself is on the stack but s points to somewhere else, and that somewhere else happens to be read-only. But char s[] is just something on the stack. So that's another example of a segfault happening. We saw that ./buggy1 resulted in a segfault. In theory, you shouldn't look at buggy1.c immediately. Instead, we'll look at it through gdb. Notice that when you get Segmentation fault (core dumped), you get this file over here called core. If we ls -l, we'll see that core is usually a pretty big file. This is the number of bytes of the file, so it looks like it's 250-something kilobytes. The reason for this is that what the core dump actually is is when your program crashes, the state of memory of your program just gets copied and pasted into this file. It gets dumped into that file. This program, while it was running, happened to have a memory usage of around 250 kilobytes, and so that's what got dumped into this file. Now you can look at that file if we do gdb buggy1 core. We can just do gdb buggy1, and that will just start up gdb regularly, using buggy1 as its input file. But if you do gdb buggy1 core, then it's specifically going to start up gdb by looking at that core file. And you saying buggy1 means gdb knows that that core file comes from the buggy1 program. So gdb buggy1 core is going to immediately bring us to where the program happened to terminate. We see here Program terminated with signal 11, Segmentation fault. We happen to see a line of assembly, which probably isn't very helpful. But if you type bt or backtrace, that's going to be the function that gives us the list of our current stack frames. So backtrace. It looks like we only have two stack frames. The first is our main stack frame, and the second is the stack frame for this function that we happen to be in, which looks like we only have the assembly code for. So let's go back into our main function, and to do that we can do frame 1, and I think we can also do down, but I almost never do down--or up. Yeah. Up and down. Up brings you up one stack frame, down brings you down a stack frame. I tend to never use that. I just specifically say frame 1, which is go to the frame labeled 1. Frame 1 is going to bring us into main stack frame, and it says right here the line of code we happen to be at. If we wanted a couple more lines of code, we can say list, and that's going to give us all the lines of code around it. The line we segfaulted at was 6: if (strcmp("CS50 rocks", argv[1]) == 0). If it isn't obvious yet, you can get it straight from here just by thinking why it segfaulted. But we can take it one step further and say, "Why would argv[1] segfault?" Let's print argv[1], and it looks like it's 0x0, which is the null pointer. We're strcmping CS50 rocks and null, and so that's going to segfault. And why is argv[1] null? [student] Because we didn't give it any command-line arguments. Yeah. We didn't give it any command-line arguments. So ./buggy1 is only going to have argv[0] be ./buggy1. It's not going to have an argv[1], so that's going to segfault. But if, instead, I do just CS50, it's going to say You get a D because that's what it's supposed to do. Looking at buggy1.c, it's supposed to print "You get a D"-- If argv[1] is not "CS50 rocks", "You get a D", else "You get an A!" So if we want an A, we need this to compare as true, which means that it compares to 0. So argv[1] needs to be "CS50 rocks". If you want to do that on the command line, you need to use \ to escape the space. So CS50\ rocks and You get an A! If you don't do the backslash, why does this not work? [student] It's two different arguments. >>Yeah. Argv[1] is going to be CS50, and argv[2] is going to be rocks. Okay. Now ./buggy2 is going to segfault again. Instead of opening it with its core file, we'll just open up buggy2 directly, so gdb buggy2. Now if we just run our program, then it's going to say Program received signal SIGSEGV, which is the segfault signal, and this is where it happened to happen. Looking at our backtrace, we see that we were in the function oh_no, which was called by the function dinky, which was called by the function binky, which was called by main. We can also see the arguments to these functions. The argument to dinky and binky was 1. If we list the function oh_no, we see that oh_no is just doing char** s = NULL; *s = "BOOM"; Why would that fail? [student] You can't dereference the null pointer? >>Yeah. This is just saying s is NULL, regardless if that happens to be a char**, which, depending on how you interpret it, it could be a pointer to a pointer to a string or an array of strings. It's s is NULL, so *s is dereferencing a null pointer, and so this is going to crash. This is one of the quickest ways you can possibly segfault. It's just declaring a null pointer and immediately segfaulting. That's what oh_no is doing. If we go up one frame, then we're going to get into the function that called oh_no. I need to do that down. If you don't enter a command and you just hit Enter again, it will just repeat the previous command that you ran. We are in frame 1. Listing this frame, we see here is our function. You can hit list again, or you can do list 20 and it will list more. The function dinky says if i is 1, then go to the oh_no function, else go to the slinky function. And we know i is 1 because we happen to see up here that dinky was called with the argument 1. Or you can just do print i and it will say i is 1. We are currently in dinky, and if we go up another frame, we know we'll end up in binky. Up. Now we're in binky. Listing this function--the list from before half cut me off-- it started off as if i is 0, then we're going to call it oh_no, else call dinky. We know i was 1, so it called dinky. And now we're back in main, and main is just going to be int i = rand() % 3; That is just going to give you a random number that is either 0, 1, or 2. It's going to call binky with that number, and it will return 0. Looking at this, just walking through the program manually without running it immediately, you would set a break point at main, which means that when we run the program your program runs up until it hits a break point. So running the program, it will run and then it will hit the main function and stop running. Now we're inside of main, and step or next is going to bring us to the next line of code. You can do step or next. Hitting next, now i has been set to rand() % 3, so we can print the value of i, and it will say i is 1. Now it does matter whether we use next or step. I guess it mattered in the previous one, but we would want to use next. If we use step, we step into the function, which means look at the actual thing that's happening inside of binky. If we use next, then it means go over the function and just go to the next line of code in our main function. Right here on this line, I was at where it said rand() % 3; if I did step, it would go into the implementation of rand and look at what's happening there, and I could step through the rand function. But I don't care about the rand function. I just want to go to the next line of code in main, so I use next. But now I do care about the binky function, so I want to step into that. Now I'm in binky. The first line of code is going to say if (i == 0), I take a step, we see we end up at dinky. If we list things, we see that it checked is i = 0. i is not equal to 0, so it went to the else condition, which is going to call dinky(i). You might get confused. If you just look at these lines directly, you might think if (i == 0), okay, then I took a step and now I'm at dinky(i), you might think that must mean i = 0 or something. No. It just means that it knows it can stick directly to the line dinky(i). Because i is not 0, the next step isn't going to end at the else. Else isn't a line it's going to stop at. It's just going to go to the next line it can actually execute, which is dinky(i). Stepping into dinky(i), we see if (i == 1). We do know i = 1, so when we step, we know we're going to end up in oh_no because i = 1 calls the function oh_no, which you can step into, which is going to set char** s = to NULL and immediately "BOOM". And then actually looking at the implementation of buggy2, this, i is just getting a random number--0, 1, or 2--calling binky, which if i is 0 it calls oh_no, else it calls dinky, which comes up here. If i is 1, call oh_no, else call slinky, which coming up here, if i is 2, call oh_no. I don't even think there is a way-- Does anyone see a way of making this a program that won't segfault? Because unless I'm missing something, if i is 0, you'll immediately segfault, else you go to a function which if i is 1 you segfault, else you go to a function where if i is 2 you segfault. So no matter what you do, you segfault. I guess one way of fixing it would be instead of doing char** s = NULL, you could malloc space for that string. We could do malloc(sizeof)--sizeof what? [student] (char) * 5? >>Does this seem right? I'm assuming this will work if I actually ran it, but it's not what I'm looking for. Look at the type of s. Let's add int*, so int* x. I would do malloc(sizeof(int)). Or if I wanted an array of 5, I would do (sizeof(int) * 5); What if I have an int**? What would I malloc? [student] Size of the pointer. >>Yeah. (sizeof(int *)); Same thing down here. I want (sizeof(char *)); This is going to allocate space for the pointer that points to "BOOM". I don't need to allocate space for "BOOM" itself because this is basically equivalent to what I said before of char *x = "BOOM". "BOOM" already exists. It happens to exist in the read-only region of memory. But it already exists, which means this line of code, if s is a char**, then *s is a char * and you're setting this char * to point to "BOOM". If I wanted to copy "BOOM" into s, then I would need to allocate space for s. I'll do *s = malloc(sizeof(char) * 5); Why 5? Why not 4? It looks like "BOOM" is 4 characters. >>[student] The null character. Yeah. All of your strings are going to need the null character. Now I can do something like strcat-- What is the function for copying a string? [student] cpy? >>strcpy. man strcpy. So strcpy or strncpy. strncpy is a bit safer since you can specify exactly how many characters, but here it doesn't matter because we know. So strcpy and look in the arguments. The first argument is our destination. The second argument is our source. We're going to copy into our destination *s the pointer "BOOM". Why might you want to do this with a strcpy instead of just what we had before of *s = "BOOM"? There is a reason you might want to do this, but what is that reason? [student] If you want to change something in "BOOM". >>Yeah. Now I can do something like s[0] = 'X'; because s points to the heap and that space on the heap that s is pointing to is a pointer to more space on the heap, which is storing "BOOM". So this copy of "BOOM" is being stored in the heap. There are technically two copies of "BOOM" in our program. There's the first one that's just given by this "BOOM" string constant, and the second copy of "BOOM", strcpy created the copy of "BOOM". But the copy of "BOOM" is being stored on the heap, and the heap you're free to change. The heap is not read-only, so that means that s[0] is going to let you change the value of "BOOM". It's going to let you change those characters. Questions? Okay. Moving on to buggy3, let's gdb buggy3. We just run it and we see we get a segfault. If we backtrace, there are only two functions. If we go up into our main function, we see that we segfaulted at this line. So just looking at this line, for (int line = 0; fgets this stuff does not equal NULL; line++). Our previous frame was called _IO_fgets. You'll see that a lot with built-in C functions, that when you get the segfault, there will be really cryptic function names like this _IO_fgets. But that's going to relate to this fgets call. Somewhere inside here, we are segfaulting. If we look at the arguments to fgets, we can print buffer. Let's print as a--Oh, no. Print isn't going to work exactly as I want it to. Let's look at the actual program. Buffer is a character array. It's a character array of 128 characters. So when I say print buffer, it's going to print those 128 characters, which I guess is what is expected. What I was looking for is print the address of buffer, but that doesn't really tell me much. So when I happen to say up here x buffer, it shows me 0xbffff090, which, if you remember from earlier or some point, Oxbffff tends to be a stack-ish region. The stack tends to start somewhere just under 0xc000. Just by seeing this address, I know that buffer is happening on the stack. Restarting my program, run, up, buffer we saw was this sequence of characters that are pretty much meaningless. Then printing file, what does file look like? [student] Null. >>Yeah. File is an of type FILE*, so it is a pointer, and the value of that pointer is null. So fgets is going to try to read from that pointer in an indirect way, but in order to access that pointer, it has to dereference it. Or, in order to access what it should be pointing to, it dereferences it. So it's dereferencing a null pointer and it segfaults. I could have restarted it there. If we break at our main point and run, the first line of code is char* filename = "nonexistent.txt"; That should give a pretty big hint as to why this program fails. Typing next brings me to the next line, where I open this file, and then I immediately get into our line, where once I hit next, it's going to segfault. Does anyone want to throw out a reason why we might be segfaulting? [student] File doesn't exist. >>Yeah. This is supposed to be a hint that whenever you're opening a file you need to check that the file actually exists. So here, "nonexistent.txt"; When we fopen filename for reading, we then need to say if (file == NULL) and say printf("File does not exist!" or--better yet--filename); return 1; So now we check to see if it's NULL before actually continuing and trying to read from that file. We can remake it just to see that that works. I intended to include a new line. So now nonexistent.txt does not exist. You should always check for this sort of thing. You should always check to see if fopen returns NULL. You should always check to make sure that malloc doesn't return NULL, or else you segfault. Now buggy4.c. Running. I'm guessing this is waiting for input or possibly infinite looping. Yes, it's infinite looping. So buggy4. It looks like we're infinite looping. We can break at main, run our program. In gdb, as long as the abbreviation you use is unambiguous or special abbreviations that they provide for you, then you can use n to use next instead of having to type out next all the way. And now that I've hit n once, I can just hit Enter to keep going next instead of having to hit n Enter, n Enter, n Enter. It looks like I'm in some kind of for loop that's setting array[i] to 0. It looks like I am never breaking out of this for loop. If I print i, so i is 2, then I'll go next. I'll print i, i is 3, then I'll go next. I'll print i and i is 3. Next, print i, i is 4. Actually, print sizeof(array), so size of array is 20. But it looks like there's some special gdb command for going until something happens. It's like setting a condition on the value of the variable. But I don't remember what it is. So if we keep going-- What were you saying? What did you bring up? [student] Does display i add-- >>Yeah. So display i can help. If we just display i, it will put up here what the value of i is so I don't have to print it out each time. If we just keep going next, we see 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5. Something is going terribly wrong, and i is being reset to 0. Looking at buggy4.c, we see all that happens is int array[5]; for (i = 0; i <= sizeof(array); i++) array[i] = 0; What do we see that's wrong here? As a hint, when I was doing the gdb buggy4--let's break main, run-- I did print sizeof(array) just to see what the condition is where I should finally break out. Where am I? Did I run? I didn't declare yet. So print sizeof(array) and that's 20, which is expected since my array is of size 5 and it's of 5 integers, so the entire thing should be 5 * sizeof(int) bytes, where sizeof(int) tends to be 4. So sizeof(array) is 20. What should this be? [student] Divided by sizeof(int). >>Yeah, / sizeof(int). It looks like there's still a problem here. I think this should just be < since it's pretty much always < and never <=. Now let's think about why this was actually broken. Does anyone have guesses why was i reset to 0 through each iteration of the loop? The only thing inside of here that's happening is that array[i] is being set to 0. So somehow, this line of code is causing our int i to be set to 0. [student] Could it be because it's overriding the memory of this part of i when it thinks it's the next element of array? >>[Bowden] Yes. When we're going beyond the end of our array, somehow that space that we're overriding is overriding the value of i. And so if we look into buggy4, break main, run, let's print the address of i. It looks like it's bffff124. Now let's print the address of array[0]. 110. What about [1]? 114. [2], 118. 11c, 120. array[5] is bfff124. So array[5] has the same address as i, which means that array[5] is i. If they have the same address, they are the same thing. So when we set array[5] to 0, we are setting i to 0. And if you think about this in terms of the stack, int i is declared first, which means i gets some space on the stack. Then array[5] is allocated, so then 20 bytes are allocated on the stack. So i gets allocated first, then these 20 bytes get allocated. So i happens right before the array, and because of the way, like I said last week, where technically the stack grows down, when you index into an array, we are guaranteed that the 0th position in the array always happens before the first position in the array. This is kind of how I drew it last week. Notice that at the bottom we have address 0 and at the top we have address Max. The stack is always growing down. Let's say we allocate i. We allocate integer i, which means let's just say up here integer i gets allocated. Then we allocate our array of 5 integers, which means that underneath that, since the stack is growing down, those 5 integers get allocated. But because of how arrays work, we're guaranteed that the first position in the array always has an address less than the second thing in the array. So array position 0 always has to happen first in memory, whereas array position 1 has to happen after that and array position 2 has to happen after that, which means that array position 0 would happen somewhere down here, array position 1 would happen above that because moving up means higher addresses since the maximum address is up here. So array[0] down here, array[1] up here, array[2] up here, array[3] up here. Notice how before we allocated integer i all the way up here, as we move further and further into our array, we are getting closer and closer to our integer i. It just so happens that array[5], which is one position beyond our array, is exactly where integer i happened to be allocated. So that's the point where we happen to be hitting the space on the stack that was allocated for integer i, and we're setting that to 0. That's how that works. Questions? Yeah. [student] Never mind. Okay. [student] How do you avoid these sort of errors? These sort of errors? Don't use C as your programming language. Use a language that has array bounds checking. As long as you're careful, you just need to avoid going past the bounds of your array. [student] So here when we went past the bounds of your array-- [Bowden] That's where things start going wrong. >>[student] Oh, okay. As long as you stay within the memory allocated for your array, you're fine. But C does no error checking. If I do array[1000], it will gladly just modify whatever happens-- It goes to the beginning of the array, then it goes 1000 positions after and sets it to 0. It doesn't do any checking that oh, this doesn't actually have 1000 things in it. 1000 is way beyond what I should be changing, whereas Java or something you'll get array out of bounds index or index out of bounds exception. That's why a lot of higher level languages have these things where if you go beyond the bounds of the array, you fail so that you can't change things from underneath you and then things go much worse than just getting an exception saying that you went beyond the end of the array. [student] And so should we have just changed the <= to just >[Bowden] Yeah. It should be < sizeof(array) / sizeof(int); since sizeof(array) is 20, but we only want 5. >>[student] Right. More questions? Okay. [student] I have a question. >>Yeah. [student] What is the actual array variable? [Bowden] Like what is array? Array itself is a symbol. It is just the address of the start of the 20 bytes that we are referencing. You can think of it as a pointer, but it is a constant pointer. As soon as things get compiled, the variable array does not exist anymore. [student] So how does it find the size of array? Size of array refers to the size of that block that that symbol refers to. When I do something like printf("%p\n", array); let's run it. What did I just do wrong? Array 'array' declared here. Oh, up here. Clang is clever, and it happens to notice that I declared the array as 5 elements but I'm indexing into position 1000. It can do that because these are just constants. It can only go so far in noticing that I'm going beyond the bounds of the array. But notice before when we had i be incorrect, it can't possibly determine how many values i could take on, so it can't determine that i was going beyond the end of the array. That's just Clang being clever. But now make buggy4. So what else am I doing wrong? Implicitly declaring library function 'printf'. I'm going to want to #include . Okay. Now running buggy4. Printing the value of the array like I did here, printing it as a pointer prints something that looks like this--bfb8805c--which is some address that's in the stack-ish region. Array itself is like a pointer, but it isn't an actual pointer, since a regular pointer we can change. Array is just some constant. The 20 blocks of memory start at address 0xbfb8805c. So bfb8805c through this address +20--or I guess -20-- is all of the memory allocated for this array. Array, the variable itself is not stored anywhere. When you're compiling, the compiler--hand wave at it-- but the compiler will just use where it knows array to be. It knows where that array starts, and so it can always just do things in terms of offsets from that beginning. It doesn't need a variable itself to represent array. But when I do something like int *p = array; now p is a pointer which points to that array, and now p actually does exist on the stack. I'm free to change p. I can do p = malloc. So it originally pointed to array; now it points to some space on the heap. I cannot do array = malloc. If Clang is clever, it will yell at me right off the bat. Actually, I'm pretty sure gcc would do this too. So array type 'int [5]' is not assignable. You cannot assign something to an array type because array is just a constant. It is a symbol which references those 20 bytes. I can't change it. [student] And where is the size of the array stored? [Bowden] It's not stored anywhere. It's when it's compiling. So where is the size of array stored? You can only use sizeof(array) inside of the function that the array is declared itself. So if I do some function, foo, and I do (int array[]) printf("%d\n", sizeof(array)); and then down here I call foo(array); inside of this function--let's run it. This is Clang being clever again. It's telling me that sizeof on array function parameter will return size of 'int *'. This would be an error if it's not what I wanted to happen. Let's actually turn off Werror. Warning. Warnings are fine. It will still compile as long as it has a warning. ./a.out is going to print 4. The warning that was generated is a clear indicator of what went wrong. This int array is just going to print sizeof(int *). Even if I put array[5] in here, it's still just going to print sizeof(int *). So as soon as you pass it into a function, the distinction between arrays and pointers is nonexistent. This happens to be an array that was declared on the stack, but as soon as we pass that value, that 0xbf blah, blah, blah into this function, then this pointer points to that array on the stack. So that means that sizeof only applies in the function that the array was declared, which means that when you are compiling this function, when Clang goes through this function, it sees array is an int array of size 5. So then it sees sizeof(array). Well, that's 20. That's actually how sizeof basically works for almost all cases. Sizeof is not a function; it's an operator. You don't call the sizeof function. Sizeof(int), the compiler will just translate that to 4. Got it? Okay. [student] So what is the difference between sizeof(array) in main and in foo? This is because we're saying sizeof(array), which is of type int *, whereas the array down here is not of type int *, it's an int array. [student] So if you had the parameter in array[] instead of int * array, would that mean that you could still change array because now it's a pointer? [Bowden] Like this? >>[student] Yeah. Can you change array within the function now? [Bowden] You could change array in both cases. In both of these cases you are free to say array[4] = 0. [student] But can you make array point to something else? [Bowden] Oh. Yeah. In either case-- >>[student] Yeah. [Bowden] The distinction between array[] and an int * array, there is none. You can also get some multidimensional array in here for some convenient syntax, but it's still just a pointer. This means that I am free to do array = malloc(sizeof(int)); and now point somewhere else. But just like how this works forever and always, changing this array by making it point to something else does not change this array down here because it's a copy of the argument, it's not a pointer to that argument. And actually, just as more indication that it's exactly the same-- we already saw what printing array prints-- what if we print the address of the array or the address of the address of the array to either of those? Let's ignore this one. Okay. This is fine. It's now running ./a.out. Printing array, then printing the address of the array, are the same thing. Array just doesn't exist. It knows when you're printing array, you're printing the symbol that refers to those 20 bytes. Printing the address of the array, well, array doesn't exist. It doesn't have an address, so it just prints the address of those 20 bytes. As soon as you compile down, like in your compiled buggy4 ./a.out, array is nonexistent. Pointers exist. Arrays do not. The blocks of memory representing the array still exist, but the variable array and variables of that type do not exist. Those are like the main differences between arrays and pointers are as soon as you make function calls, there is no difference. But inside of the function that the array itself is declared, sizeof works differently since you're printing the size of the blocks instead of the size of the type, and you can't change it because it's a symbol. Printing the thing and the address of the thing prints the same thing. And that's pretty much it. [student] Could you say that one more time? I might have missed something. Printing array and address of array prints the same thing, whereas if you print a pointer versus the address of the pointer, the one thing prints the address of what you're pointing to, the other prints the address of the pointer on the stack. You can change a pointer; you can't change an array symbol. And sizeof pointer is going to print the size of that pointer type. So int* p sizeof(p) is going to print 4, but int array[5] print sizeof(array) is going to print 20. [student] So int array[5] will print 20? >>Yes. That's why inside of buggy4 when it used to be sizeof(array) this was doing i < 20, which is not what we wanted. We want i < 5. >>[student] Okay. [Bowden] And then as soon as you start passing in the functions, if we did int* p = array; inside of this function, we can basically use p and array in exactly the same ways, except for the sizeof problem and the changing problem. But p[0] = 1; is the same as saying array[0] = 1; And as soon as we say foo(array); or foo(p); inside of the foo function, this is the same call twice. There is no difference between these two calls. Everyone good on that? Okay. We have 10 minutes. We'll try to get through this Hacker Typer program, this website, which came out last year or something. It's just supposed to be like you type randomly and it prints out-- Whatever file it happens to have loaded is what it looks like you're typing. It looks like some kind of operating system code. That's what we want to implement. You should have a binary executable named hacker_typer that takes in a single argument, the file to "hacker type." Running the executable should clear the screen and then print out one character from the passed-in file each time the user presses a key. So whatever key you press, it should throw away and instead print a character from the file that is the argument. I'll pretty much tell you what the things we're going to need to know are. But we want to check out the termios library. I have never used this library in my entire life, so it has very minimal purposes. But this is going to be the library we can use to throw away the character you hit when you are typing into standard in. So hacker_typer.c, and we're going to want to #include . Looking at the man page for termios--I'm guessing it's terminal OS or something-- I don't know how to read it. Looking at this, it says to include these 2 files, so we'll do that. First thing first, we want to take in a single argument, which is the file we should open. So what do I want to do? How do I check to see I have a single argument? [student] If argc equals it. >>[Bowden] Yeah. So if (argc != 2) printf("usage: %s [file to open]"). So now if I run this without providing a second argument--oh, I need the new line-- you'll see it says usage: ./hacker_typer, and then the second argument should be the file I want to open. Now what do I do? I want to read from this file. How do I read from a file? [student] You open it first. >>Yeah. So fopen. What does fopen look like? [student] Filename. >>[Bowden] Filename is going to be argv[1]. [student] And then what you want to do with it, so the-- >>[Bowden] Yeah. So if you didn't remember, you could just do man fopen, where it's going to be a const char *path where path is filename, const char *mode. If you happen to not remember what mode is, then you can look for mode. Inside of man pages, the slash character is what you can use to search for things. So I type /mode to search for mode. n and N are what you can use to cycle through the search matches. Here it says the argument mode points to a string beginning with one of the following sequences. So r, Open text file for reading. That's what we want to do. For reading, and I want to store that. The thing is going to be a FILE*. Now what do I want to do? Give me a second. Okay. Now what do I want to do? [student] Check if it's NULL. >>[Bowden] Yeah. Any time you open a file, make sure that you're successfully able to open it. Now I want to do that termios stuff where I want to first read my current settings and save those into something, then I want to change my settings to throw away any character that I type, and then I want to update those settings. And then at the end of the program, I want to change back to my original settings. So the struct is going to be of type termios, and I'm going to want two of those. The first one is going to be my current_settings, and then they're going to be my hacker_settings. First, I'm going to want to save my current settings, then I'm going to want to update hacker_settings, and then way at the end of my program, I want to revert to current settings. So saving current settings, the way that works, we man termios. We see that we have this int tcsetattr, int tcgetattr. I pass in a termios struct by its pointer. The way this will look is--I've already forgotten what the function was called. Copy and paste it. So tcgetattr, then I want to pass in the struct that I'm saving the information in, which is going to be current_settings, and the first argument is the file descriptor for the thing I want to save the attributes of. What the file descriptor is is like any time you open a file, it gets a file descriptor. When I fopen argv[1], it gets a file descriptor which you are referencing whenever you want to read or write to it. That's not the file descriptor I want to use here. There are three file descriptors you have by default, which are standard in, standard out, and standard error. By default, I think it's standard in is 0, standard out is 1, and standard error is 2. So what do I want to change the settings of? I want to change the settings of whenever I hit a character, I want it to throw that character away instead of printing it to the screen. What stream--standard in, standard out, or standard error-- responds to things when I type at the keyboard? >>[student] Standard in. >>Yeah. So I can either do 0 or I can do stdin. I'm getting the current_settings of standard in. Now I want to update those settings, so first I'll copy into hacker_settings what my current_settings are. And how structs work is it will just copy. This copies all of the fields, as you would expect. Now I want to update some of the fields. Looking at termios, you would have to read through a lot of this just to see what you would want to look for, but the flags you're going to want to look for are echo, so ECHO Echo input characters. First I want to set--I've already forgotten what the fields are. This is what the struct looks like. So input modes I think we want to change. We'll look at the solution to make sure that's what we want to change. We want to change lflag in order to prevent needing to look through all these. We want to change local modes. You would have to read through this entire thing to understand where everything belongs that we want to change. But it's inside of local modes where we're going to want to change that. So hacker_settings.cc_lmode is what it's called. c_lflag. This is where we get into bitwise operators. We're kind of out of time, but we'll go through it real quick. This is where we get into bitwise operators, where I think I said one time long ago that whenever you start dealing with flags, you're going to be using bitwise operator a lot. Each bit in the flag corresponds to some sort of behavior. So here, this flag has a bunch of different things, where all of them mean something different. But what I want to do is just turn off the bit which corresponds to ECHO. So to turn that off I do &= ¬ECHO. Actually, I think it's like tECHO or something. I'm just going to check again. I can termios it. It's just ECHO. ECHO is going to be a single bit. ¬ECHO is going to mean all bits are set to 1, which means all flags are set to true except for the ECHO bit. By ending my local flags with this, it means all flags that are currently set to true will still be set to true. If my ECHO flag is set to true, then this is necessarily set to false on the ECHO flag. So this line of code just turns off the ECHO flag. The other lines of code, I'll just copy them in the interest of time and then explain them. In the solution, he said 0. It's probably better to explicitly say stdin. Notice that I'm also doing ECHO | ICANON here. ICANON refers to something separate, which means canonical mode. What canonical mode means is usually when you're typing out the command line, standard in does not process anything until you hit newline. So when you do GetString, you type a bunch of things, then you hit newline. That's when it's sent to standard in. That's the default. When I turn off canonical mode, now every single character you press is what gets processed, which is usually kind of bad because it's slow to process these things, which is why it's good to buffer it into entire lines. But I want each character to be processed since I don't want it to wait for me to hit newline before it processes all the characters I've been typing. This turns off canonical mode. This stuff just means when it actually processes characters. This means process them immediately; as soon as I am typing them, process them. And this is the function which is updating my settings for standard in, and TCSA means do it right now. The other options are wait until everything that is currently on the stream is processed. That doesn't really matter. Just right now change my settings to be whatever is currently in hacker_typer_settings. I guess I called it hacker_settings, so let's change that. Change everything to hacker_settings. Now at the end of our program we're going to want to revert to what is currently inside of normal_settings, which is going to just look like &normal_settings. Notice I haven't changed any of my normal_settings since originally getting it. Then to just change them back, I pass them back at the end. This was the update. Okay. Now inside of here I'll just explain the code in the interest of time. It's not that much code. We see we read a character from the file. We called it f. Now you can man fgetc, but how fgetc is going to work is just it's going to return the character that you just read or EOF, which corresponds to the end of the file or some error happening. We are looping, continuing to read a single character from the file, until we've run out of characters to read. And while we're doing that, we wait on a single character from standard in. Every single time you type something at the command line, that's reading in a character from standard in. Then putchar is just going to put the char we read up here from the file to standard out. You can man putchar, but it's just putting to standard out, it's printing that character. You could also just do printf("%c", c); Same idea. That's going to do the bulk of our work. The last thing we're going to want to do is just fclose our file. If you don't fclose, that's a memory leak. We want to fclose the file we originally opened, and I think that's it. If we make that, I already got problems. Let's see. What did it complain about? Expected 'int' but argument is of type 'struct _IO_FILE*'. We'll see if that works. Only allowed in C99. Augh. Okay, make hacker_typer. Now we get more useful descriptions. So use of undeclared identifier 'normal_settings'. I didn't call it normal_settings. I called it current_settings. So let's change all of that. Now passing argument. I'll make this 0 for now. Okay. ./hacker_typer cp.c. I also didn't clear the screen at the beginning. But you can look back to the last problem set to see how you clear the screen. It's just printing some characters while this is doing what I want to do. Okay. And thinking about why this needed to be 0 instead of stdin, which should be #define 0, this is complaining that-- Before when I said that there's file descriptors but then you also have your FILE*, a file descriptor is just a single integer, whereas a FILE* has a whole bunch of stuff associated with it. The reason we need to say 0 instead of stdin is that stdin is a FILE* which points to the thing that is referencing file descriptor 0. So even up here when I do fopen(argv[1], I'm getting a FILE* back. But somewhere in that FILE* is a thing corresponding to the file descriptor for that file. If you look at the man page for open, so I think you'll have to do man 3 open--nope-- man 2 open--yeah. If you look at the page for open, open is like a lower-level fopen, and it's returning the actual file descriptor. fopen does a bunch of stuff on top of open, which instead of returning just that file descriptor returns a whole FILE* pointer inside of which is our little file descriptor. So standard in refers to the FILE* thing, whereas 0 refers to just the file descriptor standard in itself. Questions? [laughs] Blew through that. All right. We're done. [laughs] [CS50.TV]