Subtitles section Play video Print subtitles DAN ARMENDARIZ: Hi. I'm Dan Armendariz . 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 2, 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, it will 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 I 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 were 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 the numbers from 1 up to some specific number. And we can see that we have a variety of issues here if we're putting 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 a debug tag. In this case this is just a debug string that I'm outputting 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 5 again, we can see that we have now output before and after and find that we have not done a clear division or a 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 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 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 above 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 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 see 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 at, 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. Most 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.