Placeholder Image

Subtitles section Play video

  • 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.