[? DAN ARMADARAS: ?] Hi, I'm [? Dan Armadaras ?]. Today, we're going to be looking at debugging. Not only are we going to talk about some techniques, but also we're going to look at some of the features contained within the CS50 IDE that allow you to easily debug a program. Just one example of something that can go wrong and it's actually something that we've already seen before. In this case, this is a C program that accepts an integer from the user, divides it by two, and provides the output back to the user. Now from what we've seen earlier in lectures, we know that this will actually cause specific types of division problems when we have odd numbers. Specifically, we'll just throw away anything after the decimal point. Now, we know that this happens to be the case. And if we run it, we can confirm our suspicions, first, by compiling. And then, by running and entering an odd number. This is nothing new. But this is actually an example of a bug that can exist within a larger program that becomes harder to track down. Even though we know what the issue is, the true crux of the matter might be trying to identify specifically where the error occurs, identifying what that problem is, and then fixing it. So provide this as an example of what might be something that we already know but can be buried within other elements of the code. So opening this other source code file as an example, this division problem is now part of a larger program. Still might be a little bit contrived, and we might be able to easily identify it, especially since we're just discussing this. But we can figure out that this problem can exist on a larger scale. If I compile this and now run it, enter an odd number, we can see that we don't get precisely the output that we may have expected. In this particular case, we might say that we want to count all of the numbers from one up to some specific number. And we can see that we have a variety of issues here if we're outputting, simply, 0 and 1 when we provide an input of 5. So we already know that there's a problem here. But we may not know precisely where this issue actually exists. Now one of the ways that we can try to fix this is something that we've already been introduced to. We can just use it on a larger scale. On line 14, we have this printf function, which allows us to print out the state of various pieces of information. And this is something that you should leverage within your program to try to figure out exactly what's happening in various lines of code. So even if this is not the final output that we actually want to produce out of this program, we still might have some debug statements where we can try to figure out precisely what is happening inside of our code. So in this case, I will printf with the debug tag. In this case, this is just a debug string that I'm up-putting so that it becomes very clear in the output of my code what it is that I want to show. And output here the number that we have computed. In this case, I might want to know precisely what is happening before and after some specific computation. So I might use a printf before and after that line of code. In this case, I could even make it a little bit more clear by saying debug before and debug after so that I don't confuse myself with multiple lines that look identical. Now if we recompile this and run it, enter a number like five again, we can see that we have now output before and after and find that we have not done a clear division or clear having of the number that we actually want to do. Now in this case, this is not really a clear output. It's not really a clear outcome that we want out of this particular program. And this is, again, a little bit contrived. But, perhaps, one of the things that we could do if the specification said that we want to divide this by 2 and add 1-- so in other words, we want to round up-- then we might know that we could do that particular thing, in this case. Now here we know that we will be able to add 1 to our halved number. Let's recompile this and confirm that this is behaving the way that we want to. We can see that now before having, we have the number 5. After having, we have the number 3, which according to our specification, is what we wanted to do. But if we look at the output here, we can see that we might have another bug altogether, which is that we are starting our count from 0. Now again, this is something that we have seen in the past and we can fix quite readily. But in this case, we also had the benefit of using the printf statement directly inside of the for loop to know precisely where that error was occurring. So printf statements are very useful in helping you determine where, precisely in your source code, a specific error is occurring. And it's also important to realize that, as we're writing code, we might have assumptions about the state of a program. Or we might have assumptions about what part of the program is actually correct or incorrect when later on as we build on that program and make it part of a complex and larger program that we realize that some aspect of that is actually buggy. Using printf can really help narrow down and identify the regions of a program that may not be behaving exactly the way that we expect, based on our assumptions. But there's other tools available, as well, that allow us to try to figure out where an error is occurring and also, specifically, what things are happening inside of the program. So using printf is very useful when we want to identify specific areas of a program that have some bug. But it also becomes tedious after a while. In this case, this is a relatively simple program with just one or two variables. And it becomes very easy for us to print out the value of those variables in the context of the larger program. But we might have a different program that has many variables. And it may not be quite so easy to use printf to try to evaluate what is happening to each one of those variables as the program is executing. There's a program that exists called a debugger program. In this case, the one that we will use is the GNU debugger, or GDB, that allows us to inspect the internal workings of a program in a much more detailed way. We can actually execute GDB from the command line here by simply typing GDB and the command that we want to debug. In this case, count. Now in this case, we can see that it brings us to a prompt that says GDB. And we can actually execute commands to GDB to actually begin execution of the program, stop it at certain points, evaluate the variables and inspect the variables that exist in the program state at that particular moment, and so on and so forth. It provides a lot of power to us. But it just so happens that the CS50 IDE also provides a GUI or a user interface for GDB that allows us to do this without needing the command line interface whatsoever or at all even. The way that I can access that is by using the debug button at the very top of the CS50 IDE. Now in the past, what we have seen is that we use the command line to compile and then run a program. The debug button does both of those steps. But it also will bring up the debugger tab on the far right that allows us to inspect a variety of properties of the program as it is executing. If I click debug, in this case, it will bring up a new tab in the console window at the very bottom. And you can see that this tab has some information at the very top. And we can largely ignore this. But one of the things that we want to notice is that it outputs the same thing that we would get if we tried to run make on the C program in the terminal window. Here, we can see it's running clang, and it has a variety of flags, and it is compiling our count.c file, which was the selected tab at the time that I hit debug. So this is very useful because now using this debug button, we can simultaneously compile and then execute the program that we actually want to run. One of the flags that is important, in this case, we've actually been using for the longest time but also just did some hand waving [INAUDIBLE], which is this one right here. In clang, it says -ggdb3. In this case, what we are telling clang, our compiler, is that we want to compile our program. But also provide what are called symbol information so that the compiler actually has access to a lot of the underlying information contained within the program. More specifically, the number of functions that I have, the names of those functions, the variables, the types that those variables are, and a variety of other things that help the debugger perform its operation. Now there's something else that's important to mention when we're discussing running a program in this way. Notice that it has actually brought up a new tab in our console along the bottom. We no longer have to interact directly with the terminal window. But this new tab is actually a terminal window. It just is specific to the running program that we have created. Notice that at the bottom, in combination with some output by clang the compiler and GDB, which we can largely ignore, it actually shows the output of our program at the very bottom. Now it's important to realize that this one window actually will show you the output from your program but also can accept input for that program, as well. So notice that says please enter a number, which is the same output that we had had in the terminal window before. But it's now shown in this new tab. I can input a number. And it will actually function as we expect showing us our debug, output, the output that might be buggy, as we've seen before. And at the very bottom, it actually has some additional output from GDP just saying that this program has completed. Now as you saw in this particular run through, it wasn't particularly useful because even though we had the debugger menu come up, this was still a running program. At no point did it actually pause execution for us to be able to inspect all of the variables contained within. There's something else that we have to do in order to get GDB to recognize that we want to pause execution of the program and not just allow it to proceed normally as we would in any other case. In order to pause execution, at some specific line, we need to create what's called a break point. And a break point is very easily created in this CS50 IDE by taking your mouse and clicking directly to the left of some specific line number. Once I do that, a red dot appears, which indicates that that line is now a break point. And the next time that I run GDB, it will stop execution at that break point when it reaches that line of code. Now this is an important thing to realize that it's not necessarily the case that every line of code is actually accessible. If I were to create a function up here, for example-- void f-- and just do a print line here-- hello world-- if I never call this function, it will be the case that, if I set a break point here, the function will never be called. And therefore, this particular break point will never actually pause execution of the program. So let's say that I correctly create a break point on some line of code that will actually be executed. Now in this case, this is the first line in the main function. So it will certainly be the case that, as soon as I begin execution, the very first line will be reached. GDB will pause execution. And then, I will be able to interact with the debugger. You can set multiple lines as breakpoints, if you would like. We can also create a line up here in this segment of code that will never be reached. And we can also set one further below. The reason that we would want to do this we'll go into a little bit more detail in just a moment. So for now, let me just disable these additional break points so that we can look at what happens when I have one single break point in my program. I have made some changes to this program. So I need to save it. I will click debug so that I can begin the compilation and then execution of the debugger. We will see that, after moments, the line that we selected as the break point is highlighted in yellow. We can also notice that in the upper right in the debug panel that the pause icon has turned into a little play icon. This means that we have pause execution, in this particular case. And hitting the Play button would allow us to resume execution at that specific point. Notice that there's a couple of other buttons available in this debug panel, as well. Step over, which allows me to execute that one line of code and step over to that line to the next one, which, in this case, would mean that the printf statement is executed. And it will then pause execution on line 13, like so. And there's also a step into function, which is useful if I have created other functions elsewhere in the source code. And I want to step into those functions rather than execute that function as a whole. But we'll look more at the step into function in just a moment. Now notice some other things that actually exist within this debug panel. We have this panel called the call stack, which shows us where exactly we are. In this case, we are inside of the main function. Our script is called count.c. And we happen to be on line 13, column one, which is precisely what the highlighted region of the source code indicates, as well. Now notice that this also shows under the local variable section all of the variables that exist within this function. It's important to note that all of the variables will appear in this local variable section within a function, even before they are defined. We can see here that we have a variable called num, has a default value of 0, and it is of type int. Now before we actually initialize all of these variables, we're not necessarily guaranteed to see a value of 0. And depending on other executions that you have performed and the state of your memory when you actually run this program, you might find that you don't see values of 0 and, instead, some other crazy numbers. But don't worry about that. It's not going to be relevant until you actually initialize the value. Now in this case, we can see that I have performed some outputs. And I'm, right now, paused execution. But in this case, what I really want to do is to now step over this line of code so that I can actually query the user for that int that we want to use in our program. Now in this case, when I hit step over, notice that the Pause or rather the Resume button has changed to this Pause button because this code is actually executing. What is happening right now is that it is waiting for us to input some information as we can see by our output text at the very bottom. So right now, this is not actually paused, even though it, sort of, appears to be because nothing is happening. But it just so happens that in my specific case on line 13, I'm waiting for user input. And so GDB is not able to inspect a program as it is running. Now the next time that I enter some input-- so I'll enter that number 5, as we've seen in the past-- hit Return, and we notice that, immediately, GDB pauses and, again, highlights the next line. But notice that now, as a result of our inputting a value, we have updated that value inside of our local variables, which is very useful to know precisely what that number was in memory. Now I can allow this program to continue playing until the end of its execution by hitting Resume. We can see that very quickly does the program finish executing with the same output that we had before, the debugger closes, and now this program has stopped completely. I show that only for the purposes of seeing what happens when we actually hit Resume. But we actually are going to want to go back into this program so that we can try to debug precisely what is happening. Now that I'm using the debugger, I may not need these debug printf statements. So I could remove them as I will do now just to go back to our simpler code that we had a moment ago. Now when I save the program and execute it, it will, again, go to that initial break point that I had on line 11. And I'll be able to inspect my variables as I want to do. It just so happens that this part isn't very interesting, And I know that I'm going to print out this statement. Please enter a number. And then, I know that I'm going to ask the user for that integer. So perhaps, I actually want to move my break point a little bit further down. You can remove break points by clicking, again, directly to the left of that line number. That red dot will disappear, indicating that that break point is now gone. Now in this case, execution has been paused. And so it's not actually going to resume in that particular instance. But I can set a break point a little bit later. And when I now resume my code, it will resume and tell the point of that break point. Again, I hit Resume. Doesn't seem like anything is happening. But that's because my code is waiting for input. I will enter a number 5, hit Enter, and now the next break point will be hit. Now in this case, this is the line of code that, before, we knew happened to be buggy. So let's evaluate what happens at this particular point in time. When a line is highlighted, this line has not yet been executed. So in this case, we can see that I have a number, which I have an integer called num that has a value 5. And I'm going to be performing some math on that number. If I step over that, we can notice that the value for num has changed in accordance with the arithmetic that we've actually done. And now that we are inside of this for loop or now that the for loop itself is highlighted, we see that we have a new variable called i that is going to be used in that for loop. Now remember before that I mentioned that sometimes you're going to see some kind of crazy numbers as default before that number or that variable is actually initialized. We can see that precisely here in the this variable called i, which has not yet been initialized at the time of highlighting. But we can see that it has some number that we wouldn't actually expect. That's OK. Don't worry about it because we have not actually initialized that number until I step over this line and the value i has been initialized to the value 1. So to see that that's actually the case, let's step over. We can now see that that line has been executed. And we are now highlighting this printf line. And we can now see how our values of i and 3 have changed over time. This is very useful to do, in fact, is to step over lines repeatedly. And you can find what actually happens inside of your for loop and what happens to the variables inside of that for loop as that program execution occurs one step at a time. Now at this point, I stepped over just enough that I now am at the end of my program. If I step over that, it will actually cease execution as we have seen in the past. Let me restart this, yet again, so that I can point something else out, as well. In this case, it is now asking me, again, for a number, which I will, again, enter. But this time, I'm going to enter in a larger number so that the for loop will iterate more times. In this case, I'm going to enter a value of 11. Now again because I'd set a break point at line 15, it's going to highlight that line. We can see that our number 11 is correctly represented in our local variables. Stepping over that, we can now watch what happens to our value of i as we proceed inside of this for loop. It gets incremented every time we reach the top of that for loop. Now one of the things that might be useful to do during execution of this program is for me to actually change the variables midstream to see what happens to my program. In this case, I can actually double click the value. Notice that it becomes a text field. Now I can enter different value altogether to see how my program behaves when I've changed that variable. Now in this case, the variable i now contains the value 10. But the program is still paused in execution. When I step over, I see that the value i, which I entered as 10, is not greater than the value of num, which immediately causes the for loop to stop executing. Now that's not the only reason why you would want to modify the variable in place. You might actually want to try to modify it so that you can continue execution of a loop or so that you can modify some value before it reaches some specific set of arithmetic that you are about to perform. So now that we actually change the value of i as the program was executing, it caused the for loop to quit prematurely because, all of a sudden, i happened to be greater than the value of num, meaning that that for loop no longer needed to be executed. Further, it happened to be the case that we changed the value of i when the line 17 was highlighted, which was the point in time that the for loop execution was actually being evaluated. If I had changed the value of i on a different line, say 19, we would have seen different behavior because line 19 would have executed before the loop condition was reevaluated. Now at this point, I'm, again, at the end of this program. And I can allow this to proceed to allow my program to quit naturally. But there's a couple of things that are important to take away from this particular discussion. You need to evaluate your own assumptions about how the code should be behaving. Any time you think that some piece of code you know happens to work, that might be a red flag to go back and evaluate, and be sure that your assumption of how that code is operating is actually true to how it is expressed in your source code. But even more to point was, when we are using the debugger, you can put breakpoints at different lines of code, which will cause the debugger to pause execution at each of those lines so that you can evaluate the memory or even change it in place. And again, remember that you can create multiple breakpoints so that you can also resume execution, skip over large portions of code, and it'll automatically pause at the next break point. There's actually more advanced features of the debugger, as well. But we'll have to refer you to some subsequent videos in order to really tease apart how to use those particular functions. For now, thank you very much for watching. And good luck debugging.