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.

  • We no longer have to interact directly with the terminal window,

  • but this new tab is actually 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 it says, please enter a number, which

  • is the same output that we had had in the terminal window before

  • but is 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 GDB 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 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 breakpoint.

  • And a breakpoint is very easily created in the 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 breakpoint, and the next time that I run GDB,

  • it will stop execution at that breakpoint

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

  • here the function will never be called and therefore

  • this particular breakpoint will never actually pause

  • execution of the program.

  • >> So let's say that I correctly create a breakpoint 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 breakpoints

  • so that we can look at what happens when I have

  • one single breakpoint 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 breakpoint

  • 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 paused execution in this particular case,

  • and hitting the Play button will 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 this 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 Variables section

  • all of the variables that exists 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 that 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've 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-- 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 the number 5 as we've seen in the past,

  • hit Return-- 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, just does 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 breakpoint that I had on line 11

  • and I will 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 with 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 breakpoint a little bit further down.

  • You can remove breakpoints by clicking, again, directly

  • to the left of that line number.

  • That red dot will disappear indicating that that breakpoint 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 breakpoint a little bit later.

  • And when I now resume my code, it will resume

  • until the point of that breakpoint.

  • 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 breakpoint 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 with-- 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 until-- as default before that number

  • or that variable is actually initialized.

  • We can see that precisely here in 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 breakpoint 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 really 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 a 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 i-- the value i which I entered as 10

  • is now 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've actually changed the value of i

  • as the program was executing, it caused the for loop

  • to quit prematurely because all 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 were 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 will automatically pause at the next breakpoint.

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

DAN ARMENDARIZ: Hi.

Subtitles and vocabulary

Click the word to look it up Click the word to find further inforamtion about it