CS50 Notes All Weeks
CS50 Notes All Weeks
Courtesy of https://cs50.harvard.edu/lectures/
Acknowledgements
Full credit to the students, teachers, staff, and volunteers at Harvard,
CS50, and EdX who helped make this course possible
This PDF is simply a quick reference to all Notes found on the website;
the only changes are to format and the creation of this page. Nothing is
added or removed from the website version of this information
Share at will. This information is open to the public, for free
This PDF is in no way associated with Harvard, CS50, or EdX.
It is compiled for free, by a volunteer who is taking the course and
wished to spread the resource to all. All of the information can be found
at the aforementioned web address. This PDF simply saves you time
Week 1
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
From Scratch to C
hello, world!
Linux Commands
Compiling
User Input
Loops
Its time to introduce our inimitable course heads: Lauren Carvalho, Rob
Bowden, Joseph Ong, R.J. Aquino, and Lucas Freitas. Feel free to reach
out to them [email protected].
Problem Set 0 has been released!
Office Hours will begin soon! There really are no dumb questions. [ ]
2
CS50 Discuss is the courses forum where you can post any and all
questions you have. Well monitor it during lecture so that if you have a
question about something David says, you can post it and well try to
respond in realtime.
If you come into this course with little or no prior background in
computer science or youd just like to have the safety net of being able to
call it quits when youre 90% done with a problem set on a Thursday
night, you should consider taking the course SAT/UNS. Trust us, youll
still learn plenty!
From Scratch to C
Recall that source code looks something like the following:
int main(void)
{
printf("hello, world\n");
}
The blue "say" puzzle piece from Scratch has now become printf and the
orange "when green flag clicked" puzzle piece has become main(void).
However, source code is not something a computer actually
understands. To translate source code into something the computer
understands, well need a compiler. A compiler is a program that takes
source code as input and produces 0s and 1s, a.k.a. object code, as
output.
We wont trouble ourselves with knowing the exact mapping between a
series of 0s and 1s and the "print" command. Rather, well content
ourselves with writing instructions at a higher level that can be
translated to a lower level. This is consistent with one of the themes of
the course: layering on top of the work of others.
Statements are direct instructions, e.g. "say" in Scratch or printf in C.
The "forever" loop from Scratch can be recreated with a while
(true) block in C. The "repeat" loop from Scratch can be recreated with
a for block in C.
Note that in C just as in Scratch, there are multiple ways of achieving the
same goals.
In C, a loop that increments a variable and announces its value would
look like so:
int counter = 0;
while (true)
{
printf("%i\n", counter);
counter++;
}
if (x < y)
{
printf("x is less than y\n");
}
else if (x > y)
{
printf("x is greater than y\n");
}
else
{
printf("x is equal to y\n");
}
hello, world!
#include <stdio.h>
int main(void)
{
printf("hello, world!\n");
}
the Dropbox folder, we instead type cd Dropbox. Now if we type make hello,
we see a bit of cryptic syntax, but afterward, we can run ./hello and see
our program execute successfully.
A single dot (.) refers to the current directory. Typing ./hello instructs
the computer to look for a program named hello in the current
directory. Type ls to see the contents of the current directory. In green,
youll see hello, which is the executable program that we just compiled.
Recall that we use a compiler to translate the source code above into the
object code that the computer can actually understand.
Linux Commands
As an aside, heres a short list of Linux commands that youll find useful:
o
ls
mkdir
cd
rm
rmdir
Compiling
When we type make
runs is as follows:
hello
clang -ggdb3 -00 -std=c99 -Wall -Werror hello.c -lcs50 -lm -o hello
User Input
To make our program more interesting, lets try asking the user for a
name and saying hello to her. To do this, we need a place to store the
users name, i.e. a variable. A variable that stores a word or a phrase is
known as a string. Lets call this variable name:
#include <stdio.h>
int main(void)
{
string name;
name = GetString();
printf("hello, David\n");
}
Before we ask the user for her name, the variable name has no value. We
shouldnt print it out as such.
GetString is a function provided in the CS50 Library written by the
staff. GetString takes in user input and passes it back to your program as
a string. The = in this case is an assignment operator, meaning place in
the left side the value of the right side.
Now when we try to compile this program, we get all sorts of errors.
When the compiler prints out this many errors, its a good idea to work
your way through them from top to bottom because the errors at bottom
might actually have been caused by the errors at the top. The topmost
error is as follows:
hello.c:5:5 error: use of undeclared identifier 'string': did you mean
'stdin'?
No, we didnt mean stdin! However, the variable type string is actually
not built in to C. Its available via the CS50 Library. To use this library,
we actually need to tell our program to include it like so:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name;
name = GetString();
printf("hello, David\n");
When we compile and run this, the program appears to do nothing: the
cursor simply blinks. This is because its waiting for the user to type
something. When we type "Rob," the program still prints out "hello,
David," which isnt quite what we intended. Lets add a line to clarify to
the user that hes supposed to type something:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name;
printf("What is your name?");
name = GetString();
printf("hello, David\n", );
}
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name;
printf("What is your name?\n");
name = GetString();
printf("hello, %s\n" name);
}
Whats between the parentheses after printf are the arguments that we
pass it. Here, we pass two arguments. %s is a placeholder for the second
argument, name, which gets inserted into the first argument.
In addition to the CS50 Library, were including stdio.h, the library the
contains the definition of printf.
Loops
Lets write a silly little program with an infinite loop:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
while (true)
{
printf("I am a buggy program");
}
}
Since the loop condition true is always true, the loop continues
executing indefinitely. Compiling and running this program prints a
whole lot of text to the terminal! You dont need to restart your
Appliance to stop the program, just type Ctrl+C.
Now lets write a counter program:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
for (int i = 0; i < 100; i++)
{
printf("I can count to %i\n", i);
}
}
Ignore the cryptic syntax for now, but know that this program counts
(very fast) to 100. What if we made a mistake and typed i >= 0 instead
of i < 100 as the second loop condition? We would unintentionally
induce an infinite loop. On Wednesday well see if this program has
finished!
1. Sunday Sunday Someday!
2. Except for once when David asked me whats the deal with the internet and cats. Duh.
Andrew Sellergren
Table of Contents
Announcements and Demos
Programming Constructs in C
Conditions
Boolean Expressions
Switches
For Loops
Variables
Functions
Teaser
Announcements and Demos
Lets begin to tease apart the syntax of our very first C program:
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
}
Next we have open and close curly braces. These encapsulate related
lines of code.
The interesting line of code in this program is the printf line. Recall that
"hello, world\n" is an example of a string. The \n is a newline character.
How would we print out a double quote? If we simply write a double
quote inside the two double quotes that enclose our string, the compiler
will freak out. We need toescape it using a backslash, so we write \".
Programming Constructs in C
Conditions
if (condition)
{
// do this
}
if (condition)
{
// do this
}
else
{
// do that
}
if (condition)
{
// do this
}
else if (condition)
{
// do that
}
else
{
// do this other thing
}
The quote strings and variables weve been passing to printf between
the parentheses are known as arguments. An argument is a value that
influences the behavior of the function.
Boolean Expressions
The Boolean operators "and" and "or" are written as && and || in C:
if (condition || condition)
{
// do this
}
switch (x)
{
case 1:
// do this
break;
case 2:
// do that
break;
default:
// do this other thing
break;
}
For Loops
Within the parentheses after the for keyword, there are three parts.
Before the first semicolon, we are initializing a variable which will be our
iterator or counter, often named i by convention. Between the two
semicolons, were providing a condition which, if true, will cause
another iteration of the loop to be executed. Finally, we provide code to
update our iterator.
Recall that i++ is shorthand for "increment the value of i by 1." This loop
continues executing so long as i < 10, which happens 10 times.
A while loop is functionally equivalent to a for loop, albeit with slightly
different syntax. Consider this code that prints out "hello, world!"
infinitely:
while (true) {
print("hello, world\n");
}
do
{
// do this again and again
}
while (condition);
Variables
int counter;
counter = 0;
int counter = 0;
This one is a little easier to read and should be considered best practice.
Functions
A function is a piece of code that can take input and can produce output.
In some cases, a function can be a so-called black box. This means that
the details of its implementation arent relevant. We dont care how it
does what it does, just that it does it.
Lets represent printf with an actual black box onstage. We can write
"hello, world" on a piece of paper to represent an argument to printf.
We then place this piece of paper in the black box and, by whatever
means, the words "hello, world" appear on the screen!
To make our hello program more dynamic, we asked the user for his or
her name and passed that to printf:
printf
Now we have name written on one piece of paper which will act as the
second argument to printf. Next we create the first argument by writing
"hello, %s\n" on another piece of paper. Finally, we place these two
pieces of paper in the black box and magically, "hello, Obasi" appears on
the screen.
Functions that we implemented in the CS50 Library ( cs50.h) include:
o
GetChar
GetDouble
GetFloat
GetInt
GetLongLong
GetString
bool
string
char
double
float
int
long long
The printf function can take many different formatting characters. Just
a few of them are:
o
%c
for char
%i
%f
for float
%lld
%s
for long
long
for string
\n
for newline
\r
\'
\"
\\
for backslash
\0
int main(void)
{
printf("hello, world\n");
}
When we type make hello-0 in the terminal window at bottom, we get all
sorts of compiler errors. At the top of these errors, which tend to
compound each other, we see:
...implicitly declaring library function 'printf'...
This error message may seem overwhelming, but try looking for
keywords. Right away we notice printf. We forgot to include the library
that contains the definition of printf:
#include <stdio.h>
int main(void)
{
printf("hello, world\n");
}
hello-1.c
To say hello to the user, we need a variable to store his or her name:
#include <stdio.h>
int main(void)
{
string name = "David";
printf("hello, %s\n", name);
}
For now, we hardcode the value "David" into the variable name. This time
when we compile (simply hit the up arrow to see previous commands in
Linux), we get the following error:
...use of undeclared identifier 'string'
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name = "David";
printf("hello, %s\n", name);
}
That doesnt help us too much, but clang does point us to the problem
are with a green caret. Turns out that in C, strings must be delimited by
double quotes, not single quotes:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
#include <cs50.h>
#include <stdio.h>
int main(void)
{
printf("State your name: ");
string name = GetString();
printf("hello, %s\n", name);
}
This program compiles and functions correctly for normal names like
Rob, Lauren, and Joseph. What about an empty name? It just prints out
"hello, "; perhaps we should use a condition and a loop so that we keep
prompting the user until he provides a non-empty name.
adder.c
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for input
printf("Give me an integer: ");
int x = GetInt();
printf("Give me another integer: ");
int y = GetInt();
// do the math
printf("The sum of %i and %i is %i!\n", x, y, x + y);
}
Notice that we dont need a separate variable to store the sum, we can
inline x + y.
conditions-0.c
conditions-0.c
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for an integer
printf("I'd like an integer please: ");
int n = GetInt();
// analyze user's input (somewhat inaccurately)
if (n > 0)
{
printf("You picked a positive number!\n");
}
else
{
printf("You picked a negative number!\n");
}
}
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// ask user for an integer
printf("I'd like an integer please: ");
int n = GetInt();
// analyze user's input
if (n > 0)
{
printf("You picked a positive number!\n");
}
else if (n == 0)
{
printf("You picked zero!\n");
}
else
{
printf("You picked a negative number!\n");
}
}
Note that to test equality, we use ==, not =, which is the assignment
operator.
Teaser
Week 2
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
Floating Points
Strings
Teaser
examples
of
"reasonable"
and
char
double
float
int
long long
#include <stdio.h>
int main(void)
{
float f = 1 / 10;
printf("%.1f\n", f);
}
Here were simply trying to store the value 0.1 in a float and print it out.
Dont forget stdio.h! The .1 in front of f means that we want to print
only one decimal place.
When we compile and run this program, however, we see 0.0 printed to
the screen. Where is the bug? Lets try printing two decimal places by
writing %.2f. Nope, we get 0.00.
The problem is that were dividing one integer by another. When you do
this, the computer assumes that you want another integer in response.
Since 0.1 is not an integer, the computer actually truncates it, throwing
away everything after the decimal point. When we actually store the
resulting integer in a float, it gets converted to a number that has a
decimal point.
floats-1.c
To fix this, we could turn the integers into floating points like so:
#include <stdio.h>
int main(void)
{
printf("%.1f\n", f);
}
floats-2.c
#include <stdio.h>
int main(void)
{
float f = (float) 1 / (float) 10;
printf("%.1f\n", f);
}
the float type has a finite number of bits, we can only use it to represent
a finite number of numbers. At some point, our numbers become
imprecise.
Dont think that this imprecision is a big deal? Perhaps this video will
convince you otherwise.
As well find out later in the semester, MySQL requires you to specify
how many bits it should use to store values.
More From Last Time
Check out last weeks notes for the syntax we used for conditions,
Boolean expressions, switches, loops, variables, and functions in C.
Recall that printf was a function that had no return value (or at least
none that we cared about), but only a side effect, that of printing to the
screen.
do-while
One type of loop we didnt look closely at is the do-while loop. Lets
write a program that insists that the user give a positive number:
#include <stdio.h>
int main(void)
{
printf("I demand that you give me a positive integer: ");
int n = GetInt();
if (n <= 0)
{
printf("That is not positive!\n")
}
}
But now if the user hasnt given us a positive number, we need to copy
and paste the GetInt() call into another branch of logic to re-prompt her.
But what if she stillhasnt given us a positive number? Obviously this
could go on forever, so we probably need some kind of loop instead.
Lets try a do-while loop:
#include <stdio.h>
int main(void)
{
do
{
printf("I demand that you give me a positive integer: ");
int n = GetInt();
}
while (n <= 0);
printf("Thanks for the %d!\n", n);
}
Notice how much improved this is! When we try to compile, though, we
get an "implicit declaration" error. We need to include the CS50 Library.
Scope
Even after adding #include <cs50.h> we get "unused variable n" and
"undeclared identifier n" errors. It would seem that we are in fact using
the variable n when we check whether its less than or equal to zero.
Likewise it would seem that n is not "undeclared" since we initialized it
within the do block. Whats wrong then? Because were
declaring n inside the do block, within the curly braces, its scope is
limited to that block. Outside of those curly braces, n effectively doesnt
exist. What we need to do is declare n outside the loop but set its value
within the loop like so:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int n;
do
{
printf("I demand that you give me a positive integer: ");
n = GetInt();
}
while (n <= 0);
printf("Thanks for the %d!\n", n);
}
#include <cs50.h>
#include <stdio.h>
int n;
int main(void)
{
do
{
printf("I demand that you give me a positive integer: ");
n = GetInt();
}
while (n <= 0);
printf("Thanks for the %d!\n", n);
}
Note that declaring a variable and not using it is not strictly an error.
However, for CS50, weve cranked up the error checking of the compiler
as a pedagogical exercise. You may have noticed a series of flags that are
passed to clang automatically when you type make. Two of those flags
are -Wall -Werror which mean "make all warnings into errors."
Strings
Theres a lot more going on under the hood with strings than weve let
on so far. Consider the following program:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("Please give me a string: ");
string s = GetString();
for (int i = 0; i < strlen(s); i++)
{
printf("%c\n", s[i]);
}
}
Check out the first jailbreak of the iPhone, the winner of an obfuscated C
contest, and a very pretty program!
Andrew Sellergren
Table of Contents
Announcements and Demos
Functions
function-0.c
function-1.c
Strings
string-1.c
string-2.c
capitalize-0.c
capitalize-1.c
capitalize-2.c
The Null Terminator
Arrays
ages.c
Cryptography
Announcements and Demos
Functions
function-0.c
Thus far, weve used functions like printf, which prints to the screen,
and GetString, which gets a string from the user, but what if we want to
write our own function? Lets revisit this program that prompts a user
for his name and try to factor out some of the logic:
#include <cs50.h>
#include <stdio.h>
void PrintName(string name)
{
printf("hello, %s\n", name);
}
int main(void)
{
printf("Your name: ");
string s = GetString();
PrintName(s);
}
Although this function is actually only one line of code, its much nicer
to write just the function name than to copy and paste that one line
when we want to reuse it.
Above the definition of main, we declared our function PrintName to have a
return type of void (because it doesnt return anything but has a side
effect) and a single argument name of type string.
Notice that although we call the users name s in main, we refer to it
as name in PrintName because that was what we chose to call the argument.
What if we had chosen to write the definition of PrintName after main? The
compiler would have complained of "implicit declaration of function
PrintName" because it reads top to bottom and we
called PrintName before we defined it. Better than defining it above main,
however, is declaring it above main and then defining it below. This
keeps main at the top of the file:
#include <cs50.h>
#include <stdio.h>
void PrintName(string name);
int main(void)
{
printf("Your name: ");
string s = GetString();
PrintName(s);
}
void PrintName(string name)
{
printf("hello, %s\n", name);
}
Lets rewrite our program that asks for a positive integer using a
function:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int n = GetPositiveInt();
printf("Thanks for the positive int!\n", n);
}
#include <cs50.h>
#include <stdio.h>
int GetPositiveInt(void);
int main(void)
{
int n = GetPositiveInt();
printf("Thanks for the %i!\n", n);
}
#include <cs50.h>
#include <stdio.h>
int GetPositiveInt(void);
int main(void)
{
int n = GetPositiveInt();
printf("Thanks for the %i!\n", n);
}
int GetPositiveInt(void)
{
int n;
do
{
printf("Give me a positive integer: ");
n = GetInt();
}
while (n <= 0);
}
#include <cs50.h>
#include <stdio.h>
int GetPositiveInt(void);
int main(void)
{
int n = GetPositiveInt();
printf("Thanks for the %i!\n", n);
}
int GetPositiveInt(void)
{
int n;
do
{
printf("Give me a positive integer: ");
n = GetInt();
}
while (n <= 0);
return n;
}
Question: main has a return type of int too, so why arent we returning
anything from it? Technically, in the version of C were using, 0 is
returned by default at the end of main. 0 means nothing went wrong,
whereas each non-zero return value could represent a different error
code.
Strings
Realize that there are two types of memory in your computer: disk,
where you store your music and photos, etc., and RAM, or random
access memory, which store information that needs to be accessed
quickly while a program is running. The memory that stores a string like
"hello" for your program is RAM.
string-1.c
Consider again the program that takes a string from the user and prints
it one character per line:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = GetString();
if (s != NULL)
{
for (int i = 0; i < strlen(s); i++)
{
printf("%c\n", s[i]);
}
}
}
Whats the deal with the s != NULL? It turns out that the GetString will
not always succeed in getting a string from the user. If it fails, perhaps
because the user typed a string that was too long to hold in
memory, GetString will return a special sentinel value named NULL.
Without this check, other things we try to do with smight cause the
program to crash.
s[i],
string-2.c
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = GetString();
if (s != NULL)
{
for (int i = 0, n = strlen(s); i < n; i++)
{
printf("%c\n", s[i]);
}
}
}
Although computers are very fast these days and this optimization may
not be immediately noticeable, its important to look for opportunities to
improve design. These little optimizations can add up over time. One of
the problem sets weve done in years past was writing a spellchecker in C
with the goal of making it as fast as possible. An optimization like this
might save a few milliseconds of runtime!
capitalize-0.c
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
string s = GetString();
for (int i = 0, n = strlen(s); i < n; i++)
{
if (s[i] >= 'a' && s[i] <= 'z')
{
printf("%c", s[i] - ('a' - 'A'));
}
else
{
printf("%c", s[i]);
}
}
printf("\n");
}
Thus far, weve worked with a few libraries of code that gave us
convenient functions. Lets add one more to that list:
o
stdio.h
cs50.h
string.h
ctype.h
#include
#include
#include
#include
<cs50.h>
<ctype.h>
<stdio.h>
<string.h>
int main(void)
{
string s = GetString();
for (int i = 0, n = strlen(s); i < n; i++)
{
if (islower(s[i]))
{
printf("%c", toupper(s[i]));
}
else
{
printf("%c", s[i]);
}
}
printf("\n");
}
capitalize-2.c
We dont strictly need the curly braces around if-else blocks so long as
they are only a single line. However, theres an even better way to
shorten this program:
#include
#include
#include
#include
<cs50.h>
<ctype.h>
<stdio.h>
<string.h>
int main(void)
{
string s = GetString();
for (int i = 0, n = strlen(s); i < n; i++)
{
printf("%c", toupper(s[i]));
}
printf("\n");
}
Turns out that toupper handles both lowercase and uppercase characters
properly, so we dont even need the islower check. We know this because
we checked the man page, or manual page, for toupper by typing man
toupper at the command line. This page tells us that the return value is
the converted letter or the original letter if conversion was not possible.
Perfect!
The Null Terminator
Strings are actually a special case of a data type called an array. Arrays
allow us to store related variables together in one place. For example,
consider a program that stores and prints out the ages of everyone in the
room:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// determine number of people
int n;
do
{
printf("Number of people in room: ");
n = GetInt();
}
while (n < 1);
// declare array in which to store everyone's age
int ages[n];
// get everyone's age
for (int i = 0; i < n; i++)
{
printf("Age of person #%i: ", i + 1);
ages[i] = GetInt();
}
// report everyone's age a year hence
printf("Time passes...\n");
for (int i = 0; i < n; i++)
{
printf("A year from now, person #%i will be %i years old.\n", i +
1, ages[i] + 1);
}
}
The first lines should be familiar to you by now: were prompting the
user for a positive number. In line 16, we use that number as the
number of places in our array called ages. ages is a bucket with room
for n integers. Using an array is a better alternative than declaring
an int for every single person in the room, especially since we dont even
know how many there are until the user tells us!
The rest of the program is pretty straightforward. We iterate
through ages the same way we iterated through strings, accessing each
element using square bracket notation.
Cryptography
Week 3
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
Command-line Arguments
argv-0.c
argv-1.c
argv-2.c
Debugging
debug.c
GDB
Security
Announcements and Demos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// determine number of people
int n;
do
{
printf("Number of people in room: ");
n = GetInt();
}
while (n < 1);
// declare array in which to store everyone's age
int ages[n];
// get everyone's age
for (int i = 0; i < n; i++)
{
printf("Age of person #%i: ", i + 1);
ages[i] = GetInt();
}
// report everyone's age a year hence
printf("Time passes...\n");
for (int i = 0; i < n; i++)
{
printf("A year from now, person #%i will be %i years old.\n", i +
1, ages[i] + 1);
}
}
Thus
Here, we pass two arguments, argc and argv. argc is an int and argv is an
array of string. argc actually indicates how many arguments weve
passed to a program.
Earlier, when we typed ./ages at the command line, there was 1
command-line argument: the name of the program itself. If we had
typed ./ages hello world, there would have been 3 command-line
arguments. In these cases, argc would have taken the values 1 and 3,
respectively. There is always at least 1 command-line argument.
Whereas argc contains
the
number
of
command-line
arguments, argv contains the command-line arguments themselves.
argv-0.c
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
printf("%s\n", argv[1]);
}
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
int x = atoi(argv[1]);
printf("%d\n", x);
}
#include <cs50.h>
#include <stdio.h>
int main(int argc, string argv[])
{
if (argc < 2)
{
printf("Not enough command line arguments\n");
}
int x = atoi(argv[1]);
printf("%d\n", x);
}
argv-1.c
// print arguments
printf("%s\n", argv[i]);
}
}
argv-2.c
Now, to go even deeper [ ], lets print each character of each commandline argument on its own line:
1
#include <cs50.h>
#include <stdio.h>
#include <string.h>
argv[i][j]
int my_strlen(string s)
{
int length = 0
while(s[length] != '\0')
{
length++;
}
return length;
}
We could even move this logic into our second loop and avoid a function
call altogether:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(int argc, string argv[])
{
// print arguments
for (int i = 0; i < argc; i++)
{
for (int j = 0; argv[i][j] != '\0'; j++)
{
printf("%c\n", argv[i][j]);
}
}
The best way to learn to debug is to work with buggy code like the
following:
#include <stdio.h>
#include <cs50.h>
void foo(int i)
{
printf("%i\n", i);
}
int main(void)
{
printf("Enter an integer: ");
int i = GetInt();
while (i > 10)
{
i--;
}
while (i != 0)
{
i = i - 3;
}
foo(i);
}
Lets assume that the user provides an integer greater than 10 (a bad
assumption). The first while loop will then decrement i by 1 until it
equals 10, at which point the loop condition i > 10 will no longer be true
and the loop will exit. The second while loop will decrement i by 3 until
it equals 0. But if it starts at 10, i will go to 7, then 4, then -1, then -4,
and so on to negative infinity. Thats not what we intended!
One useful debugging technique is to add some printf statements:
#include <stdio.h>
#include <cs50.h>
void foo(int i)
{
printf("%i\n", i);
}
int main(void)
{
printf("Enter an integer: ");
int i = GetInt();
printf("Outside first while loop");
while (i > 10)
{
printf("First while loop: %i\n", i);
i--;
}
printf("Outside second while loop");
while (i != 0)
{
printf("Second while loop: %i\n", i);
i = i - 3;
}
foo(i);
}
When we compile and run debug.c now, we can clearly see that the
second while loop is infinite.
As your programs get longer and more complicated, youll find more
sophisticated debugging techniques more useful.
GDB
run make on your code thus far, the compiler clang has executed with a
command-line argument -ggdb3. This instructs clang to compile your
code so that you can debug it within GDB if you so choose.
After youve compiled debug.c, you can open it in GDB by executing gdb
debug. Note that the name of the program you want to walk through, in
this case debug, is provided as a command-line argument to gdb.
Once youve started GDB, youll find yourself at a prompt. Type run to
begin the execution of your program. This alone isnt very useful, as the
program will finish executing just as it would outside of GDB.
Before typing run, you can type break main to insert a breakpoint at the
beginning of the main function. A breakpoint is a place at which
execution of the program is paused. Once youve reached a breakpoint,
you can type next repeatedly to step through your code line by line.
Hitting Enter will redo the last command you typed.
Whenever we want to view the value of i, we can type print i.
list
will display the code before and after your current position.
#include <stdio.h>
#include <cs50.h>
void foo(int i)
{
while (i != 0)
{
i = i - 3;
}
printf("%i\n", i);
}
int main(void)
{
printf("Enter an integer: ");
int i = GetInt();
while (i > 10)
{
i--;
}
foo(i);
}
After starting GDB by executing gdb debug, well again set a breakpoint at
the beginning of main by typing break
main.
If we next over
the foo function call, the program will finish executing. Note that
although the second while loop appears infinite, it will eventually
terminate for reasons well wave our hands at for now.
If we want to examine what foo is doing, we need to type step instead
of next. step is identical to next if the next line of code is not a function
call.
We can also set a breakpoint at the foo function by typing break foo. Now
when we type run, well first stop at main. If we type continue, execution
will resume until we hit the next breakpoint at foo.
Security
But what if the malicious code is in the compiler? Then the compiler
might actually insert the backdoor into the login prompt even though
the code for the login prompt seems safe and has been reviewed. We can
hope again that this would be caught by one of the people who reviewed
the code for the compiler.
But what if the malicious code is in the compiler that is used to compile
the compiler? Well, then, the backdoor might get inserted without
anyone knowing.
If you think this scenario is unlikely, consider the speech that Ken
Thompson gave, Reflections on Trusting Trust, when he accepted the
Turing Award (more or less the Nobel Prize of computer science). In it,
he describes this exact technique for compromising a compiler so that it
would introduce a backdoor into a login program. The login program he
refers to, however, is not some toy program, but rather the login
program for all of UNIX. Since delivering this speech, Thompson has
confirmed that this exploit was actually implemented and released to at
least one company, BBN Technologies.
1. Even deeper.
Last updated 2014-03-19 09:24:33 PDT
Week 3, continued
Andrew Sellergren
Table of Contents
Announcements and Demos
Searching
Sorting
Bubble Sort
Selection Sort
Insertion Sort
Big O Notation
Announcements and Demos
If youre struggling with the C syntax, dont worry, its normal! In a few
weeks, youll look back on Problem Set 2 and be amazed at how far
youve come.
Searching
Imagine there are 7 doors with numbers behind them and you want to
find the number 50. If you know nothing about the numbers, you might
just have to open the doors one at a time until you find 50 or until all the
doors are opened. That means it would take 7 steps, or more
generally, n steps, where n is the number of doors. We might call this
a linear algorithm.
How might our approach change if we know that the numbers are
sorted? Think back to the phonebook problem. We can continually
divide the problem in half! First we open the middle door. Lets say its
16. Then we know that 50 should be in the doors to the right of the
middle, so we can throw away the left. We then look at the middle door
in the right half and so on until we find 50! This algorithm
has logarithmic running time, the green line in the graph below:
Note that there are plenty of algorithms that are much worse than
linear, as this graph shows:
Although it looks like n3 is the worst, 2n is much worse for large inputs.
Sorting
Bubble Sort
If the numbers arent sorted to begin with, how much time will it take to
sort them before we search?
To start figuring this out, lets bring 7 volunteers on stage and have them
hold pieces of paper with the numbers 1 through 7 on them. If we ask
them to sort themselves, it seems to only take 1 step as they apply an
algorithm something like "if theres a smaller number to my right, move
to the right of it." In reality, though, it takes more than 1 step as there
are multiple moves going on.
In order to count the number of steps this algorithm takes, well slow it
down and allow only 1 move to happen at a time. So walking left to right
among the volunteers, we examine the two numbers next to each other
and if theyre out of order, we swap them. We may have to walk left to
right more than 1 time in order to finish sorting. How do we know when
theyre sorted? As a human, you can look at it and know, but we need a
way for the computer to know. If we walk left to right among the
volunteers and make 0 swaps, then we can be sure that all the numbers
are in the right order. That means well need to store the number of
swaps made in a variable that we check after each walkthrough.
This algorithm we just described is called bubble sort. To describe its
running time, lets generalize and say that the number of volunteers is n.
Each time we walk through the volunteers, were taking n-1 steps. Lets
just round that up and call it n. How many times do we walk left to right
through the volunteers? In the worst case scenario, the numbers will be
perfectly out of order, that is, arranged left to right largest to smallest. In
order to move the 1 from the right side all the way to the left side, were
going to have to walk through the volunteers n times. So thats n steps
per walkthrough and n walkthroughs, so the running time is n2.
Selection Sort
This algorithm is called selection sort. Heres what our numbers look
like after each walkthrough:
4
1
1
1
1
1
1
2
2
2
2
2
2
2
6
6
6
3
3
3
3
1
4
4
4
4
4
4
3
3
3
6
6
5
5
7
7
7
7
7
7
6
5
5
5
5
5
6
7
4 2 6 1 3 7 5
2 6 1 3 7 5
4 6 1 3 7 5
4 6 1 3 7 5
2 4 6 3 7 5
2 3 4 6 7 5
2 3 4 5 6 7
2 3 4 5 6 7
4
2
2
1
1
1
1
Note that the space is merely to delimit the sorted list from the rest of
the list, but the total list is still only size 7.
Big O Notation
linear search
binary search
log n
bubble sort
n2
selection sort
n2
n2
insertion sort
n2
In the best case for linear and binary search, the number youre looking
for is the first one you examine, so the running time is just 1. In the best
case for our sorting algorithms, the list is already sorted, but in order to
verify that in bubble sort, we need to walk through the list at least once.
Unfortunately, to verify that in selection sort, we still have to
do n2 walkthroughs, each of which confirms that the smallest number is
in the correct position.
What about the best case for insertion sort? Well fill in that blank next
time.
Are we doomed to n2 running time for sorting? Definitely not. Check
out this visualization to see how fast merge sort is compared to bubble
sort, selection sort, and insertion sort. Merge sort leverages the same
"divide and conquer" technique that binary search does.
Last updated 2013-09-27 19:50:45 PDT
Week 4
Andrew Sellergren
Table of Contents
Announcements and Demos
Teaser
noswap.c
Announcements and Demos
We seem to have caused some confusion with our coupon code policy.
Apologies! What we meant was to incentivize you to start your problem
sets early. To explain it more clearly:
o by default, psets are due on Thu at 12pm
o if you start early, finishing part of pset by Wed at 12pm (and
receive a coupon code), you can extend your deadline for rest of
pset to Fri at 12pm
o coupon-code problem still required even if not completed by Wed
at 12pm
From Last Time
We talked a little more high level about searching and sorting. Bubble
sort, for example, gets its name from the way that large numbers bubble
up toward the end of the list. We visualized bubble sort using this
website.
We quantified the efficiency of searching and sorting algorithms using
big O notation. Bubble sort was said to take n2 steps in the worst case,
where n was the size of the input.
Selection sort gets its name from selecting the smallest number on each
walkthrough of the list and placing it at the front. Unfortunately,
selection sort also took n2steps in the worst case.
Insertion sort involved sorting the list in place, but required shifting
elements of the "sorted list" to the right whenever a smaller number
needed to be placed in the middle of it. It too took n2 steps.
Whats the best sorting algorithm? Why not ask President Obama?
When talking about the running time of algorithms, O represents the
upper bound, the worst case, whereas `\Omega` represents the lower
bound, the best case. For bubble sort, selection sort, and insertion sort,
the worst case was that the list was in exactly reverse order and the best
case was that the list was already sorted. Whereas bubble sort (with a
variable tracking the number of swaps) and insertion sort only
took n steps in the best case, selection sort still took n2 steps.
So we say that bubble sort, selection sort, and insertion sort are
all O(n2). Linear search is O(n). Binary search is O(log n). Finding the
length of an array is in O(1), a.k.a. constant time, if that length is stored
in a variable. printf is also O(n) since it takes n steps to
print n characters.
Bubble sort and insertion sort are `\Omega`(n). Linear search and
binary search are `\Omega`(1) because the element were looking for
might just be the first one we examine.
One algorithm we mentioned briefly was merge sort. Merge sort is both
`\Omega`(n log n) and O(n log n), so we say it is `\Theta`(n log n).
Merge Sort
To see how merge sort compares to the other algorithms weve looked at
so far, check out this animation. Notice that bubble sort, insertion sort,
and selection sort are the three worst performers! The flip side is that
they are relatively easy to implement.
On input of n elements:
If n < 2
Return.
Else:
Sort left half of elements.
Sort right half of elements.
Merge sorted halves.
If n is less than 2, then its either 0 or 1 and the list is already sorted.
This is the trivial case.
If n is greater than or equal to 2, then what? We seem to be copping out
with a circular algorithm. Two of the steps begin with the command
"sort" without giving any indication as to how we go about that. When
we say "sort," what we actually mean is reapply this whole algorithm to
the left half and the right half of the original list.
Will this algorithm loop infinitely? No, because after youve halved the
original list enough times, you will eventually have less than 2 items left.
Okay, so were halving and halving and halving until we have less than 2
items and then were returning. So far, nothing seems sorted. The magic
must be in the "Merge sorted halves" step.
One consideration with merge sort is that we need a second list for
intermediate storage. In computer science, theres generally a tradeoff
between resources and speed. If we want to do something faster, we may
need to use more memory.
To visualize merge sort, lets bring 8 volunteers on stage. Well hand
them numbers and sit them down in chairs so that theyre in the
following order:
4 2 6 1 3 7 5 8
The bold numbers are the ones were currently focusing on. Merge sort
says to first sort the left half, so lets consider:
4 2 6 1 3 7 5 8
Now we again sort the left half:
4 2 6 1 3 7 5 8
And again:
4 2 6 1 3 7 5 8
Now we have a list of size 1, so its already sorted and we return.
Backtracking, we look at the right half of the final two-person list:
4 2 6 1 3 7 5 8
Again, a list of size 1, so we return. Finally, we arrive at a merge step.
Since the elements are out of order, we need to put them in the correct
order as we merge:
_ _ 6 1 3 7 5 8
2 4 _ _ _ _ _ _
From now on, the red numbers will represent the second list we use for
intermediate storage. Now we focus on the right half of the left half of
the original list:
_ _ 6 1 3 7 5 8
2 4 _ _ _ _ _ _
We insert these two numbers in order into our intermediate list:
_ _ _ _ 3 7 5 8
2 4 1 6 _ _ _ _
Now we merge the left and right half of the intermediate list:
_ _ _ _ 3 7 5 8
1 2 4 6 _ _ _ _
Finally, we can insert the intermediate list back into the original list:
1 2 4 6 3 7 5 8
And were done with the "Sort left half" step for the original list!
Repeat for the right half of the original list, skipping to the "Sort left
half" step:
1 2 4 6 _ _ 5 8
_ _ _ _ 3 7 _ _
Sort right half:
1 2 4 6 _ _ _ _
_ _ _ _ 3 7 5 8
Merge:
1 2 4 6 _ _ _ _
_ _ _ _ 3 5 7 8
Move the right half back to the original list:
1 2 4 6 3 5 7 8
Now, merge the left half and the right half of the original list:
_ _ _ _ _ _ _ _
1 2 3 4 5 6 7 8
And ta-da!
1 2 3 4 5 6 7 8
Merge sort is O(n log n). As before, the log n comes from the dividing by
two. The n thus must come from the merging. You can rationalize this
by considering the last merge step. To figure out which number to place
in the intermediate array next, we point our left hand at the leftmost
number of the left half and our right hand at the leftmost number of the
right half. Then we walk each hand to the right and compare numbers.
All told, we walk through every number in the list, which takes n steps.
Check out Robs visualization of merge sort. You can even hear what
sorting algorithms sound like.
A function that calls itself is using recursion. In the above pseudocode,
we implemented merge sort using recursion.
A Little Math
To show mathematically that merge sort is O(n log n), lets use the
following notation:
T(n) = 0, if n < 2
So far, all this says is that it takes 0 steps to sort a list of 1 or 0 elements.
This is the so-called base case.
T(n) = T(n/2) + T(n/2) + n, if n > 1
This notation indicates that the rest of the algorithm, the recursive case,
i.e. sorting a list of n elements, takes as many steps as sorting its two
halves, each of n / 2 elements, plus an extra n steps to do the merging.
Consider the case where n = 16:
T(16) = 2 * T(8) + 16
T(8) = 2 * T(4) + 8
T(4) = 2 * T(2) + 4
T(2)
T(1)
= 2 * T(1) + 2
= 0
Since the base case, where a list of 0 or 1 is already sorted, takes 0 steps,
we can now substitute 0 in for T(1) and calculate T(2):
T(2) = 2 * 0 + 2
= 2
T(16)
T(8)
T(4)
T(2)
T(1)
=
=
=
=
=
2
2
2
2
1
*
*
*
*
24
8
2
0
+
+
+
+
16
8
4
2
Thus, T(16) is 64. This number is actually n log n. Dividing the list
successively accounts for log n, but the additional n factor comes from
the merge step.
Here again with merge sort weve returned to the idea of "divide and
conquer" that we saw in Week 0 with the phonebook example.
In case you want to know what recursion is, try Googling it and checking
out the "Did you mean" suggestion. Hooray for geek humor!
More with Recursion
sigma-0.c
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int n;
do
{
Recall the sigma symbol (`\Sigma`) which stands for sum. It makes
sense, then, to call our summing function sigma:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
int n;
do
{
printf("Positive integer please: ");
n = GetInt();
}
while (n < 1);
int answer = sigma(n);
printf("%i\n", answer);
}
#include <cs50.h>
#include <stdio.h>
int sigma(int m);
int main(void)
{
int n;
do
{
printf("Positive integer please: ");
n = GetInt();
}
while (n < 1);
int answer = sigma(n);
printf("%i\n", answer);
}
int sigma(int m)
{
if (m < 1)
{
return 0;
}
int sum = 0;
for (int i = 1; i <= m; i++)
{
sum += i;
}
return sum;
}
int sigma(int m)
{
if (m <= 0)
{
return 0;
}
else
{
return (m + sigma(m - 1));
}
}
You might worry that this implementation will induce an infinite loop.
However, the first if condition represents a base case in
which sigma doesnt call itself.
Teaser
noswap.c
Consider the following code that claims to swap the values of two
integers:
#include <stdio.h>
void swap(int a, int b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i\n", x);
printf("y is %i\n", y);
printf("Swapping...\n");
swap(x, y);
printf("Swapped!\n");
printf("x is %i\n", x);
printf("y is %i\n", y);
}
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
x is 1
y is 2
Swapping...
Swapped!
x is 1
y is 2
Obviously, the numbers havent really been swapped. Well find out why
next time!
Last updated 2013-10-03 00:26:15 PDT
Week 4, continued
Andrew Sellergren
Table of Contents
Announcements and Demos
Pointers
noswap.c
swap.c
compare-0.c
copy-0.c
compare-1.c
copy-1.c
Teaser
Announcements and Demos
Fifth Monday is on 10/7! This is the deadline for changing your grading
status in the course. Switching to SAT/UNS or Pass/Fail requires a
signature, so please do approach David, Rob, or Lauren if you need.
Pointers
noswap.c
Pointers are one of the more complex topics we cover, so dont feel bad if
your mind feels stretched in the next few weeks. Thats a good thing!
Recall last time we ended with a function that didnt live up to its name:
#include <stdio.h>
void swap(int a, int b);
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i\n", x);
printf("y is %i\n", y);
printf("Swapping...\n");
swap(x, y);
printf("Swapped!\n");
printf("x is %i\n", x);
printf("y is %i\n", y);
}
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
#include <stdio.h>
int main(void)
{
int x = 1;
int y = 2;
printf("x is %i\n", x);
printf("y is %i\n", y);
printf("Swapping...\n");
int tmp = x;
x = y;
y = tmp;
printf("Swapped!\n");
printf("x is %i\n", x);
printf("y is %i\n", y);
}
So why does this logic work in main but not in swap? a and b are actually
copies of x and y, so when we swap a and b, x and y are unchanged.
One way to fix this would be to make x and y global variables, declaring
them outside of main. In fifteen.c, it made sense to make certain
variables global because they were to be used by the whole program.
However, in a small program like noswap.c, using global variables is
sloppy design.
swap.c
address. Lets say the int that a points to is stored at the 123 rd byte of
RAM. The value of a then, is 123. To get at the actual integer value thats
stored at byte 123, we write *a. *a = *b says "store at location a whatever
is at location b."
Now that weve changed swap, we need to change how we call swap.
Instead of passing x and y, we want to pass the address of x and the
address of y:
swap(&x, &y)
&
Lets assume our integers 1 and 2 are stored next to each other in
memory and 1 is stored at byte 123. That means 2 is stored 4 bytes away
(since an int requires 4 bytes), so well assume that its stored at byte
127. The values of a and b, then, are 123 and 127. We can simulate
passing those to swap by writing them on pieces of paper and putting
them in a black box.
We ask a volunteer to come onstage and retrieve the pieces of paper
from the black box. Next he needs to allocate a little bit of memory for
variable tmp. In tmp, he stores the value of the int whose address is in a.
This is 1.
Next, at address 123, he erases the number 1 and writes in the number 2.
This corresponds to the *a = *b line, which says "store at
location a whatever is at locationb."
Finally, at address 127, he erases the number 2 and writes in the number
2, which was stored in tmp. tmp is a local variable, but goes away
when swap returns.
compare-0.c
For the first few weeks, we have worked with string as a data type.
However, this is a type that we defined for you in the CS50 Library.
A string is really achar*. Its the address of a char. In fact, its the address
of the first char in the string.
Consider the following program which claims to compare two strings:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
// get line of text
printf("Say something: ");
string s = GetString();
// get another line of text
printf("Say something: ");
string t = GetString();
// try (and fail) to compare strings
if (s == t)
{
printf("You typed the same thing!\n");
}
else
{
printf("You typed different things!\n");
}
}
Here, we simply ask the user for two strings and store them in s and t.
Then we ask if s == t. Seems reasonable, no? Weve used the == operator
for all the other data types weve seen thus far.
But if we compile and run this program, typing "hello" twice, we always
get "You typed different things!"
Recall that a string is just an array of characters, so "hello" looks like
this in memory:
e l l o \0
Although were able to access the first character "h" using bracket
notation, under the hood its really located at one of 2 billion or so
memory addresses. Lets call it address 123 again. Then "e" is at address
124, "l" is at address 125, and so on. A char only takes 1 byte, so this time
the memory addresses are only 1 apart.
If GetString is getting us this string, then what does it actually return?
The number 123! Before it does so, it allocates the memory necessary to
store "hello" and inserts those characters along with the null terminator.
But if we only know the memory address of the first character, how do
we know how long the string is? Recall that strings end with the
special \0 character, so we can just iterate until we find it.
copy-0.c
Lets take a look at a program that tries, but fails to copy a string:
#include
#include
#include
#include
<cs50.h>
<ctype.h>
<stdio.h>
<string.h>
int main(void)
{
// get line of text
printf("Say something: ");
string s = GetString();
if (s == NULL)
{
return 1;
}
// try (and fail) to copy string
string t = s;
// change "copy"
printf("Capitalizing copy...\n");
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// print original and "copy"
printf("Original: %s\n", s);
printf("Copy:
%s\n", t);
}
We check that s isnt NULL in case the user has given us more characters
than we have memory for. NULL is actually the memory address 0. By
convention, no user data can ever be stored at byte 0, so if a program
tries to access this memory address, it will crash.
Now that we have the user-provided string in s, we assign the value
of s to t. But if s is just a memory address, say 123, then t is now the
same memory address. Both s and t are pointing to the same chunks of
memory.
To prove that this program is buggy, well try to capitalize t, but not s.
The output, though, shows that both s and t are capitalized.
To emphasize that their role is to point to other variables, pointers are
often represented as arrows.
compare-1.c
#include <cs50.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
// get line of text
printf("Say something: ");
char* s = GetString();
// get another line of text
printf("Say something: ");
char* t = GetString();
// try to compare strings
if (s != NULL && t != NULL)
{
if (strcmp(s, t) == 0)
{
printf("You typed the same thing!\n");
}
else
{
printf("You typed different things!\n");
}
}
}
Now that we know a string is really just a char*, we need to be careful its
not NULL.
copy-1.c
#include
#include
#include
#include
<cs50.h>
<ctype.h>
<stdio.h>
<string.h>
int main(void)
{
// get line of text
printf("Say something: ");
char* s = GetString();
if (s == NULL)
{
return 1;
}
// allocate enough space for copy
char* t = malloc((strlen(s) + 1) * sizeof(char));
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 }
if (t == NULL)
{
return 1;
}
// copy string, including '\0' at end
int n = strlen(s);
for (int i = 0; i <= n; i++)
{
t[i] = s[i];
}
// change copy
printf("Capitalizing copy...\n");
if (strlen(t) > 0)
{
t[0] = toupper(t[0]);
}
// print original and copy
printf("Original: %s\n", s);
printf("Copy:
%s\n", t);
// success
return 0;
In line 17, were declaring a pointer t and initializing it with the return
value of a function named malloc. malloc takes a single argument, the
number of bytes of memory requested, and returns the address in
memory of the first of those bytes or NULL if the memory couldnt be
allocated.
In this case, were allocating enough memory for all the characters
in s plus 1 extra for the null terminator. We multiply this number of
characters by sizeof(char), which gives the size in bytes of a char on this
particular operating system. Normally it will be 1, but were handling
other cases correctly, too.
Once we have enough memory, we iterate through all of the characters
in s and assign them one at a time to t.
Teaser
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)
{
int* x;
int* y;
x = malloc(sizeof(int));
*x = 42;
*y = 13;
y = x;
}
*y = 13;
Week 5
Andrew Sellergren
Table of Contents
Announcements and Demos
GDB is a debugger that allows you to walk through your programs step
by step, printing variables and examining the stack as you go. Although
the interface is somewhat arcane, it will save you hours of time in the
long run. Note that you can use this for Problem Set 4, as the GUI part
of Breakout will pop up in a GWindow separate from your terminal.
A string is really just a char*. This pointer stores the address of the first
character of the string. We only need to know the address of the first
character because we can iterate through the rest of the characters until
we hit the null terminator. Memory addresses are often prefixed with
0x, which indicates that the number is inhexadecimal, or base 16.
Hexadecimal makes use of 16 possible digits, 0-9 as well as A-F, to
represent very large numbers quite concisely.
Note that when you allocate memory, you cant trust what it contains
until youve initalized it. Before it is initialized, newly allocated memory
contains garbage values that well often denote with question marks.
Question: why arent we using the dereferencing operator to access the
characters of a string? The square bracket notation is just syntactic
sugar. When we write *s, its equivalent to s[0]. However, if we wanted
to access character i of s, wed have to write *(s+i), which is less
intuitive but equivalent to s[i]. s is just a number representing the
memory address of the first character, so adding i to it gives us the
address of character i.
Our first attempt at implementing a swap function failed because, by
default, arguments to functions are passed as copies. When we
defined swap so that it took two pointers as arguments, it worked as
intended.
Memory
The Stack
The text segment contains the actual 0s and 1s of your program. The
initialized data and unitialized data segments contain global variables.
The stack is used to store local variables and function parameters.
We left off last time with some code that put Binky in a tough spot:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main(void)
{
int* x;
int* y;
x = malloc(sizeof(int));
*x = 42;
*y = 13;
y = x;
*y = 13;
}
What gets stored in x at line 6? The address of the first byte of memory
allocated by malloc.
Unfortunately, in line 10, we dereference the pointer y before it has been
initialized. y contains some garbage value that we interpret as a memory
address, so when we try to access it, bad things happen. In the video,
You may know this as a popular website, but it actually has a specific
technical meaning. If a programmer forgets to check the boundaries of
an array, he or she leaves his program vulnerable to an attack that can
take over control of the program. Consider the following code:
#include <string.h>
void foo(char* bar)
{
char c[12];
memcpy(c, bar, strlen(bar));
}
int main(int argc, char* argv[])
{
foo(argv[1]);
}
For a thorough discussion of this attack, check out the Wikipedia article.
In short, this program passes the first command-line argument to a
function foo that writes it into an array of size 12. If the first commandline argument is less than 12 characters long, everything works fine. If
the first command-line argument is greater than 12 characters long,
then it will overwrite memory past the bounds of c. If the first
command-line argument is greater than 12 characters long and actually
contains the address in memory of some malicious code, then it could
potentially overwrite the return address of foo. When foo returns, then,
it will give control of the program over to this malicious code rather
than main.
Instead of ending on a scary note, lets end with a joke.
Last updated 2013-10-10 00:17:08 PDT
Week 5, continued
Andrew Sellergren
Table of Contents
Announcements and Demos
Compiling
Memory
The Stack
The Heap
Valgrind
Memory
The Stack
At the top, the text segment contains the actual 0s and 1s of the
program. Below that are the initialized data and uninitialized data
segments that contain global variables. Well talk more about the heap
later.
The stack is the segment of memory on which frames are layered for
each function call, including main. In swap.c, we saw that we could
manipulate the frame ofmain while within swap if we passed it pointers to
variables in the scope of main.
We also learned that if we dont check the bounds of our arrays, we leave
our programs susceptible to stack overflows, or buffer overrun attacks.
This is an exploit by which an adversary passes input to a program that
overwrites memory it shouldnt have access to.
The Heap
#include <cs50.h>
#include <stdio.h>
int main(void)
{
printf("State your name: ");
string name = GetString();
printf("hello, %s\n", name);
}
#include <cs50.h>
#include <stdio.h>
int main(void)
{
printf("State your name: ");
char* name = GetString();
printf("hello, %s\n", name);
}
#include <stdio.h>
void foo(void)
{
foo();
}
int main(void)
{
foo();
}
HEAP SUMMARY:
in use at exit: 6 bytes in 1 blocks
Those 6 bytes are the ones that were allocated to store the string
"David."
#include <cs50.h>
#include <stdio.h>
int main(void)
{
printf("State your name: ");
char* name = GetString();
printf("hello, %s\n", name);
free(name);
}
Now when we run this through Valgrind, we see the following output:
#include <stdlib.h>
void f(void)
{
int* x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void)
{
f();
}
This line refers to the fact that we tried to write 4 bytes (an int) to a
chunk of memory that doesnt really belong to our program.
Valgrind also tells us that were not freeing the 40 bytes of memory
that x points to.
The CS50 Library
/**
* Reads a line of text from standard input and returns the equivalent
* char; if text does not represent a char, user is prompted to retry.
* Leading and trailing whitespace is ignored. If line can't be read,
* returns CHAR_MAX.
*/
char GetChar(void)
{
// try to get a char from user
while (true)
{
// get line of text, returning CHAR_MAX on failure
string line = GetString();
if (line == NULL)
{
return CHAR_MAX;
}
// return a char if only a char (possibly with
// leading and/or trailing whitespace) was provided
char c1, c2;
if (sscanf(line, " %c %c", &c1, &c2) == 1)
{
free(line);
return c1;
}
else
{
free(line);
printf("Retry: ");
}
}
}
Structs
Just like we used typedef to create the string type in the CS50 Library,
you can use it to define your own types:
#include <cs50.h>
// structure representing a student
typedef struct
{
int id;
string name;
string house;
}
student;
Here were defining a variable type named student. Inside of this type,
which is actually a struct, there are three variables representing the ID,
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include "structs.h"
// class size
#define STUDENTS 3
int main(void)
{
// declare class
student class[STUDENTS];
// populate class with user's input
for (int i = 0; i < STUDENTS; i++)
{
printf("Student's ID: ");
class[i].id = GetInt();
printf("Student's name: ");
class[i].name = GetString();
printf("Student's house: ");
class[i].house = GetString();
printf("\n");
}
// now print anyone in Mather
for (int i = 0; i < STUDENTS; i++)
{
if (strcmp(class[i].house, "Mather") == 0)
{
printf("%s is in Mather!\n\n", class[i].name);
}
}
// free memory
for (int i = 0; i < STUDENTS; i++)
{
free(class[i].name);
free(class[i].house);
}
There are many different file formats used to store images. One such
format is a bitmap, or BMP. A very simple bitmap might use 0 to
represent black and 1 to represent white, so a series of 0s and 1s could
store a black-and-white image.
More sophisticated file formats like JPEG store 0s and 1s for the image
itself but also metadata. In Problem Set 5, youll use this fact to detect
JPEGs that have been lost on Davids memory card.
Last updated 2013-10-12 23:33:02 PDT
Week 7
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
User Input
scanf-0.c
scanf-1.c
scanf-2.c
Structs
structs.h
structs-0.c
structs-1.c
Storage
Hard Drives
Floppy Disks
Linked Lists
Announcements and Demos
o Proposal
o Status Report
o CS50 Hackathon
o Implementation
o CS50 Fair
From Last Time
is what the CS50 Library uses to get input from the user in
functions like GetString.
sscanf
scanf-0.c
#include <stdio.h>
int main(void)
{
int x;
printf("Number please: ");
scanf("%i", &x);
printf("Thanks for the %i!\n", x);
}
scanf-1.c
#include <stdio.h>
int main(void)
{
char* buffer;
printf("String please: ");
scanf("%s", buffer);
printf("Thanks for the %s!\n", buffer);
}
#include <stdio.h>
int main(void)
{
char buffer[16];
printf("String please: ");
scanf("%s", buffer);
printf("Thanks for the %s!\n", buffer);
}
Here, you can see that scanf treats the array buffer as a memory address.
We know that the address is for a chunk of memory of size 16 bytes.
In what scenario might this program also be buggy? If the user provides
a string longer than 15 characters (not 16 because we need at least one
character for the null terminator), the program may crash with a
segmentation fault.
How do we know in advance how much memory to request for user
input? We dont! The CS50 Library has some logic that reads user input
one character at a time with scanf and requests more memory whenever
it runs out.
Structs
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string name = GetString();
string house = GetString();
}
* What if we want to store another student's information?
we need some more variables:
+
[source]
Well I guess
Hopefully, this strikes you as bad design. In prior weeks, we solved the
problem of storing numerous variables of the same types by using
arrays:
#include <cs50.h>
#include <stdio.h>
int main(void)
{
string names[3];
string house[3];
}
This solves the problem of repetitive code, but introduces the problem of
names no longer being directly associated with houses.
structs.h
string name;
string house;
}
student;
structs-0.c
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include "structs.h"
// number of students
#define STUDENTS 3
int main(void)
{
// declare students
student students[STUDENTS];
// populate students with user's input
for (int i = 0; i < STUDENTS; i++)
{
printf("Student's name: ");
students[i].name = GetString();
printf("Student's house: ");
students[i].house = GetString();
}
// now print students
for (int i = 0; i < STUDENTS; i++)
{
printf("%s is in %s.\n", students[i].name, students[i].house);
}
// free memory
for (int i = 0; i < STUDENTS; i++)
{
free(students[i].name);
free(students[i].house);
}
}
structs-1.c
Before we examine the code, lets just make and run structs-1.c. After
we enter in some data and the program exits successfully, a file
named students.csv is created. CSV stands for comma-separated values,
a very simple version of a table like you may have worked with in Excel.
The code that creates this CSV file looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include
#include
#include
#include
<cs50.h>
<stdio.h>
<stdlib.h>
<string.h>
#include "structs.h"
// number of students
#define STUDENTS 3
int main(void)
{
// declare students
student students[STUDENTS];
// populate students with user's input
for (int i = 0; i < STUDENTS; i++)
{
printf("Student's name: ");
students[i].name = GetString();
Hard drives that arent SSDs (solid-state drives with no moving parts)
consist of circular metal platters and magnetic heads that read and write
bits on them. The 0s and 1s of files are stored by magnetic particles that
are flipped with either their north or their south poles sticking up.
Somewhere on the hard drive there exists a table that maps filenames to
their memory addresses. As you can with RAM, you can number all of
the bytes of a hard drive so that each has a memory address. When you
delete a file, say by dragging it to the trash can or even by emptying the
trash can, the contents of the file may not actually be deleted. Rather,
the files entry in the location table is simply erased so that the operating
system forgets where the file was stored. Not until the 0s and 1s of the
file are actually overwritten will the files contents truly be gone. In the
meantime, the file can be recovered by software like Norton or by a
program like the one youll write for Problem Set 5. Having been
provided with the raw bytes of an SD card, youll be tasked with
searching through them to look for the particular pattern of bits that
identifies the start of a JPEG file.
Floppy Disks
Back in Davids day [ ], another type of storage called floppy disks was
popular. Functionally, these are very similar to hard drives in that inside
their plastic casing, there is a circular magnetic platter. You can get your
1
hands on it just by ripping off the metal tab. Be careful, theres a spring
in there!
These days, the size of hard drives is measured in terabytes. A so-called
"high-density" floppy disk can only store 1.44 megabytes, or roughly 1
millionth of a terabyte.
Linked Lists
Arrays are useful because they enable the storage of similar variables in
contiguous memory. One downside of arrays is that they have a fixed
size. Another downside is that theres no easy way to insert something in
the middle of an array. To do so, we would have to allocate memory for a
copy of the array and then shift all the elements to the right.
To solve the problem of fixed size, well relax the constraint that the
memory we use be contiguous. We can take a little bit of memory from
here and a little bit of memory from there just so long as we can connect
them together. This new data structure is called a linked list:
Each element of a linked list contains not only the data we want to store,
but also a pointer to the next element. The final element in the list has
the NULL pointer.
To implement a linked list, well borrow some of the syntax we used for
structs:
int n;
struct node *next;
}
node;
Pictorially, next is the bottom box that points to the next element of the
linked list. Why do we have to declare it as a struct node* then? The
compiler doesnt yet know what a node is, so we have to call it a struct
node in the meantime.
There are a few linked list operations that will be of interest to us:
o insert
o delete
o search
o traverse
The search operation is actually pretty easy to implement:
search
Andrew Sellergren
Table of Contents
Linked Lists
list-0.c
Search
Insertion
Hash Tables
Linear Probing
Separate Chaining
The Birthday Problem
Tries
Teaser
Linked Lists
Although this file isnt very complex, its conventional to put type
definitions in a separate header file. When you include that file, you use
double quotes instead of angle brackets because the file is local:
#include "list-0.h"
int main(void)
{
int c;
do
{
// print instructions
printf("\nMENU\n\n"
"1 - delete\n"
"2 - insert\n"
"3 - search \n"
"4 - traverse\n"
"0 - quit\n\n");
// get command
printf("Command: ");
c = GetInt();
// try to execute command
switch (c)
{
case 1: delete(); break;
case 2: insert(); break;
case 3: search(); break;
case 4: traverse(); break;
}
}
while (c != 0);
// free list before quitting
node* ptr = first;
while (ptr != NULL)
{
node* predptr = ptr;
ptr = ptr->next;
free(predptr);
}
}
void search(void)
{
// prompt user for number
printf("Number to search for: ");
int n = GetInt();
We declare a pointer to a node called ptr and point it to the first node in
the list. To iterate through the list, we set our while condition to be ptr !
= NULL. The last node in the linked list points to NULL, so ptr will
be NULL when weve reached the end of the list. To access the integer
within the node that ptr points to, we use the-> syntax. ptr->n is
equivalent to (*ptr).n. If the integer within the node is the one were
searching for, were done. If not, we update ptr to be the next pointer of
the current node.
Insertion
Insertion into a linked list requires handling three different cases: the
beginning, middle, and end of the list. In each case, we need to be
careful in how we update the node pointers lest we end up orphaning
part of the list.
To visualize insertion, well bring 6 volunteers onstage. 5 of these
volunteers will represent the numbers 9, 17, 22, 26, and 34 that are in
our linked list and 1 volunteer will represent the first pointer.
Now, well request memory for a new node, bringing one more volunteer
onstage. Well give him the number 5, which means that he belongs at
the beginning of the list. If we begin by pointing first at this new node,
then we forget where the rest of the list is. Instead, we should begin by
pointing the new nodes next pointer at the first node of the list. Then we
update first to point to the new node.
Again, well request memory for a new node, bringing another volunteer
onstage and assigning her the number 55. She belongs at the end of the
list. To confirm this, we traverse the list by updating ptr to the value
of next for each node. In each case, we see that 55 is greater than ptr->n,
so we advance to the next node. However, ultimately, we end up
with ptr equal to NULL because 55 is greater than all of the numbers in the
list. We dont have a pointer, then, to the last node in the list, which
means we cant update it. To prevent this, we need to keep track of the
node one to the left of ptr. Well store this in a variable called predptr in
our sample code. When we reach the end of the list, predptr will point to
the last node in the list and we can update its next value to point to our
new node.
Another solution to this problem of keeping track of the previous node is
to implement a doubly linked list. In a doubly linked list, each node has
a next pointer to point to the next node and a prev pointer to point to the
previous node.
Once more, well request memory for a new node, assigning the value 20
to our last volunteer. This time when we traverse the list, our predptr is
pointing to the 17 node and our ptr is pointing to the 22 node when we
find that ptr->n is greater than 20. To insert 20 into the list, we point
the next pointer of predptr to our new node and the next pointer of our
our new node to ptr.
Linked lists are yet another example that design is very much subjective.
They are not unilaterally better than arrays, but they may be more useful
than arrays in certain contexts. Likewise, arrays may be more useful
than linked lists in certain contexts.
Hash Tables
The holy grail of running time is O(1), i.e. constant time. Weve already
seen that arrays afford us constant-time lookup, so lets return to this
data structure and use it to store a list of names. Lets assume that our
array is of size 26, so we can store a name in the location corresponding
to its first letter. In doing so, we also achieve constant time for insertion
since we can access location i in the array in 1 step. If we want to insert
the name Alice, we index to location 0 and write it there.
This data structure is called a hash table. The process of getting the
storage location of an element is called hashing and the function that
does so is called a hash function. In this case, the hash function simply
takes the first letter of the name and converts it to a number.
Linear Probing
What problems might arise with this hash table? If we want to insert the
name Aaron, we find that location 0 is already filled. We could take the
approach of inserting Aaron into the next empty location, but then our
running time deteriorates to linear because in the worst case, we may
have to iterate through all n locations in the array to insert or search for
a name. This approach is appropriately named linear probing.
Separate Chaining
When two elements have the same hash, there is said to be a collision in
the hash table. Linear probing was our first approach to handling
collisions. Another approach is separate chanining. In separate
chaining, each location in the hash table stores a pointer to the first
node of a linked list. When a new element needs to be stored at a
location, it is simply added to the beginning of the linked list.
The Birthday Problem
Why worry at all about collisions? How likely is it really that they will
happen? It turns out the probability of collisions is actually quite high.
We can phrase this question in a slightly different way that well call the
Birthday Problem:
In a room of n CS50 students, whats the probability that at least 2
students have the same birthday?
Notice that the probability is already 0.5 when there are only 22
students in the room. By the time we consider the case where there are
58 students in the room, the probability is almost 1. The implication for
hash tables is that there are going to be collisions.
What is the worst-case running time of search in a hash table that uses
separate chaining? In the worst case, were going to have to traverse the
entire linked list at any hash table location. If we consider the number of
locations in our hash table to be m, then the lookup time for a hash table
that uses separate chaining is O(n m). m is a constant, though, so the
lookup time is really just O(n). In the real world, however, O(n m) can
be much faster than O(n).
What is the worst-case running time of insertion in a hash table that
uses separate chaining? Its actually O(1) if we always insert to the
beginning of the linked list at each hash table location.
Tries
One last data structure well discuss is a trie. The word trie comes
from the word retrieval, but is usually pronounced like try. For our
purposes, the nodes in a trie are arrays. We might use a trie to store a
dictionary of names of famous scientists, as this diagram suggests:
In this trie, each index in the array stands for a letter of the alphabet.
Each of those indices also points to another array of letters. The
symbol denotes the end of a name. We have to keep track of where
words end so that if one word actually contains another word (e.g.
Mendeleev and Mendel), we know that both words exist. In code, the
symbol could be a Boolean flag in each node:
node;
One advantage of a trie is that insertion and search times are unaffected
by the number of elements already stored. If there are n elements stored
in the trie and you want to insert the value Alice, it still takes just 5
steps, one for each letter. This runtime we might express as O(k),
where k is the length of the longest possible word. But k is a constant, so
were actually just talking about O(1), or constant-time insertion and
lookup.
Although it may seem like a trie is the holy grail of data structures, it
may not perform better than a hash table in certain contexts. Choosing
between a hash table and a trie is one of many design decisions youll
have to make for Problem Set 6.
Teaser
Week 8
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
Linked Lists
Hash Tables
Tries
Stacks
Queues
Trees
Binary Search Trees
Teaser
Announcements and Demos
Problem Set 5 is now over, but the photo contest is not! Submit the link
to your photos by noon on Monday 11/4 for a chance to win a Leap
Motion!
We offer a number of CS50 Seminars on a wide variety of topics to help
you gear up for your Final Project. Register here and check out past
seminars here. On the roster so far for this year are:
node*
Hash Tables
We also discussed hash tables, which associate keys with values. The
keys are determined by taking a deterministic hash of the values using a
hash function. In our first example, this hash function simply took the
first letter of the name that we wanted to store, 0 for Alice, 1 for Bob,
and so on. In code, this might look like:
int hash(char* s)
{
return s[0] - 'A';
}
The final data structure we examined was a trie. Both insertion time and
search time for a trie are O(k), where k is the length of the word being
inserted or searched for. But k is really a constant since words have a
finite length, so insertion time and search time are actually O(1).
Weve already seen that a programs memory is called the stack because
of the way in which function frames are layered on top of each other.
More generally, a stack is a data structure that has its own advantages
and disadvantages compared to arrays, linked lists, hash tables, and
tries.
We interact with stacks using only two basic operations: push and pop.
To add data to the stack, we push it onto the top of the stack. To retrieve
data from the stack, we pop it off the top of the stack. As a result, a stack
exhibits last in first out (LIFO) storage. The only data that we can
retrieve from the stack is the last data we added to it.
In what contexts might LIFO storage be useful? Clearly its useful for
organizing a programs memory. As well see soon, its also useful for
validating the tree structure of a web pages HTML.
We might implement a stack like so:
typedef struct
{
int trays[CAPACITY];
int size;
}
stack;
Its convenient to think of a stack like the stack of trays in the dining
halls. In the code above, CAPACITY is a constant defining the maximum
number of such trays that a stack can contain. Another integer
named size stores the number of trays currently in the stack.
If youre familiar with the lines that form outside the Apple store when a
new iPhone is released, then youre familiar with queues. We also
interact with queues using two basic operations: enqueue and dequeue.
Whereas stacks exhibit LIFO storage, however, queues exhibit FIFO, i.e.
first in first out, storage. Imagine how upset the people outside the
Apple store would be if the line were implemented as a stack instead of a
queue!
We can implement a queue using a struct:
typedef
{
int
int
int
}
queue;
struct
numbers[CAPACITY];
front;
size;
Note that our queue type is very similar to our stack type. Why do we
need the extra int for queue? front keeps track of the index of the next
value to be dequeued. If we add 9, 17, and 22 as we did to the stack and
then remove 9, we need to know that 17 should be the next value
Remember that the -> operator means to access and dereference a field
within a struct.
Note that both the first if condition and the final else condition in the
above are base cases. Its not necessary to place them both at the top of
our logic.
Teaser
Soon well start working in HTML, a markup language that allows you to
specify what a web page should look like, and JavaScript, a
programming language that allows you to execute logic within a
browser. Our first web page will be implemented like so:
<!DOCTYPE html>
<html>
<head>
<title>hello, world</title>
</head>
<body>
hello, world
</body>
</html>
Andrew Sellergren
Table of Contents
Announcements and Demos
The Internet
HTTP
DNS
TCP/IP
Announcements and Demos
How does the internet actually work? When you type facebook.com into
your browser (Chrome, Internet Explorer, Firefox, etc.), the browser
makes an HTTP request. HTTP, which stands for hypertext transfer
protocol, defines the language that the browser and web server speak to
each other. Think of a web server exactly like a server at a restaurant:
when you make a request of him, he brings it to you. In the context of
the internet, the server is bringing you a web page written in HTML.
More on HTML later.
HTTP is a protocol for browsers and servers to talk to each other.
Humans, too, have protocols for talking to each other. Consider that
when you meet someone, you often greet him or her with a handshake.
Browsers and servers also greet and acknowledge each other according
to HTTP.
Trying 31.13.69.32...
Connected to star.c10r.facebook.com.
Escape character is '^]'.
GET / HTTP/1.1
Host: www.facebook.com
GET / HTTP/1.1
Host: www.facebook.com
User-Agent:
Mozilla/5.0
(Macintosh;
AppleWebKit/537.36
(KHTML,
like
Safari/537.36
Indel
Gecko)
Mac
OS
X
10_8_5)
Chrome/30.0.1599.101
Note that these user agent strings tell websites a lot about your
computer. In this case, it tells Facebook that were using an Intel-based
Mac running OS X 10.8.5 and version 30.0.1599.101 of Chrome.
With this user agent string added to our HTTP request, we get a normal
response back from the server. The server actually still redirects us, this
time to the more secure HTTPS version of the site.
Why do we write GET /? Were requesting the root of the website. The
root of the site is denoted with a slash just as the root of a hard drive is.
If we switch gears and make an HTTP request to www.mit.edu, we get
back an HTTP response that starts with HTTP/1.1 200 OK and actually
contains the HTML that makes up their homepage. 200 is the "all is
well" HTTP status code. You can see this same HTML if you go to View
Source within your browser.
DNS
How does your browser know the IP address of MITs web server? There
are special servers called DNS, or domain name system, servers whose
job it is translate hostnames like www.mit.edu into IP addresses.
TCP/IP
TCP/IP is the protocol that defines how information travels through the
internet. Information travels from source to destination via
several routers in between. Routers are other servers that simply take in
bytes and direct them elsewhere. We can see which routers our
information passes through using a command-line program
named traceroute. Each of the lines in the output represents a router
that our request went through. Lines that are just three asterisks
represent routers that ignore this type of request, so we dont know
where they are. On the right side, there are three time values which
Week 9
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
Web Debugging Tools
Intro to HTML and PHP
HTML
PHP
More HTML
Implementing Google
Frosh IMs
froshims0.php
conditions-1.php
register-0.php
register-3.php
This week we start our foray into web programming! The fundamentals
and concepts from the first 9 weeks of the course will still play a role in
how you program, but youll find that tasks are an order of magnitude
easier to accomplish now.
After taking a course like CS50, you may start noticing mistakes in a
show like Numb3rs. First, IP addresses (at least v4 addresses), are of the
form w.x.y.z, where w, x, y, and z are all numbers between 0 and 255.
Second, the language of the code you see in Charlies browser window is
Objective C and appears to be manipulating a variable named crayon,
which has nothing to do with what Amita was ostensibly programming.
From Last Time
Browsers these days have very powerful tools for debugging. In Chrome,
theres Developer Tools, in IE, theres Developer Toolbar, and in Firefox,
theres Firebug. Chrome is installed by default on the Appliance, so well
take a peek at Developer Tools.
If you right click on any part of a web page and select Inspect Element,
the Developer Tools pane will be opened at the bottom. The Elements
tab provides a much more readable and organized version of the HTML
of the web page. The Network tab allows you to poke around the HTTP
requests that your browser executes. If we navigate to facebook.com,
well see that the first such HTTP request is met with a 301 response
code. We can also see the GET / HTTP/1.1 request that we sent if we click
on view source next to Request Headers.
Why is Facebook responding with a "Moved Permanently" status? We
didnt type www before facebook.com, so Facebook is adding it for us.
They might be doing this for technical or even branding reasons.
But the second HTTP request is also met with a 301 response code! This
time were being redirected to the HTTPS version of the site.
When our request is finally answered with some content, its a lot more
than just one page of HTML. There are a number of scripts, stylesheets,
and images that are received as well. This makes sense since web pages
consist of more than just HTML.
Intro to HTML and PHP
HTML
<!DOCTYPE html>
<html>
<head>
<title>hello, world</title>
</head>
<body>
hello, world
</body>
</html>
The first line is the doctype declaration which tells the browser "here
comes some HTML." Everything after that is enclosed in the <html> tag.
This tag has two children, <head> and <body>. Each of these children,
which begin with an open tag and end with a close tag, well call
HTML elements. Given that certain elements are children of each other,
we can use a tree structure to represent the HTML of a web page:
PHP
printf("hello, world\n");
named php. When we do so, we just get our line of code printed out to
the screen. What went wrong? PHP has tags ( <?php and ?>) which tell the
interpreter where code begins and ends. Without them, our lines of code
are interpreted as raw HTML that should be passed over. Lets add them
in:
<?php
printf("hello, world\n");
?>
4 13:29 index.html
The rw on the far left stands for "read or write." The fact that there is
only one such rw means that only the owner of this file can read it. We
want everyone to be able to read it, so we run the command chmod a+r
index.html. Now when we reload the page in our browser, we see "hello,
world."
More HTML
<!DOCTYPE html>
<html>
<head>
<title>hello, world</title>
</head>
<body>
hello, <a href="https://www.cs50.net/">CS50</a>.
</body>
</html>
is an attribute of the <a> tag. In this case, it contains the URL that
we want to link to. What goes inside the element is the text we want the
link to show: "CS50."
href
<!DOCTYPE html>
<html>
<head>
<title>hello, world</title>
</head>
<body>
<div style="background-color: red;">
Top of Page
</div>
<div>
Bottom of Page
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<style>
#top
{
background-color: #ff0000;
}
#bottom
{
background-color: #abcdef;
font-size: 24pt;
}
</style>
<title>hello, world</title>
</head>
<body>
<div id="top">
Top of Page
</div>
<div id="bottom">
Bottom of Page
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<link href="styles.css" rel="stylesheet"/>
<title>hello, world</title>
</head>
<body>
<div id="top">
Top of Page
</div>
<div id="bottom">
Bottom of Page
</div>
</body>
</html>
The <link> tag is an example of an empty tag in that there is no need for
a separate close tag. styles.css must be in the same folder as index.html,
unless we specify the full or relative path, and it must be world-readable.
Implementing Google
When you search for something on Google, the URL changes from
google.com to something with a lot of information after the /. This
information is actually a series of parameters that Google uses to create
its search results for you. One such parameter is your search query. If
you navigate to http://www.google.com/search?q=cats, youll notice
that the search term "cats" is already filled in for you. q is a key meaning
"query" and its value, "cats," is specified after the =.
Lets implement Google! First, we need an input box for a users query:
<!DOCTYPE html>
<html>
<head>
<title>CS50 Search</title>
</head>
<body>
<h1>CS50 Search</h1>
<form>
<input type="text"/>
<input type="submit"/>
</form>
</body>
</html>
With this <form> tag and a few <input> tags, we have a very simple search
enginethat doesnt do anything. We need to specify an action for
the <form> tag:
<!DOCTYPE html>
<html>
<head>
<title>CS50 Search</title>
</head>
<body>
<h1>CS50 Search</h1>
<form action="https://www.google.com/search" method="get">
<input name="q" type="text"/>
<br/>
<input type="submit" value="CS50 Search"/>
</form>
</body>
</html>
Now were telling the form to submit its information directly to Google
using the GET method. There are two methods for submitting form
information, GET and POST. For now, just know that GET means the
information is appended to the URL.
Weve also added a name attribute to the text input to match the URL
parameter we saw that Google was using. We changed the text that the
submit button displays to "CS50 Search" using its value attribute.
Finally, we added a line break using the <br/> tag between the two
inputs.
When we type "cats" and click "CS50 Search," we end up
on http://www.google.com/search?q=cats! Weve implemented Google!
Frosh IMs
froshims0.php
Back at the turn of the 19th century when David was a freshman at
Harvard, the process of registering for intramural sports was painfully
manual. You had to fill out a paper form and actually drop it off at the
dorm room of the proctor in charge. David decided to change all that by
implementing an online registration form. Although his original
implementation was in Perl, we can recreate it in HTML and PHP:
<?php
/**
* froshims-0.php
*
* David J. Malan
* [email protected]
*
* Implements a registration form for Frosh IMs.
* Submits to register-0.php.
*/
?>
<!DOCTYPE html>
<html>
<head>
<title>Frosh IMs</title>
</head>
<body style="text-align: center;">
<h1>Register for Frosh IMs</h1>
<form action="register-0.php" method="post">
Name: <input name="name" type="text"/>
<br/>
<input name="captain" type="checkbox"/> Captain?
<br/>
<input name="gender" type="radio" value="F"/> Female
<input name="gender" type="radio" value="M"/> Male
<br/>
Dorm:
<select name="dorm">
<option value=""></option>
<option value="Apley Court">Apley Court</option>
<option value="Canaday">Canaday</option>
<option value="Grays">Grays</option>
<option value="Greenough">Greenough</option>
<option value="Hollis">Hollis</option>
<option value="Holworthy">Holworthy</option>
<option value="Hurlbut">Hurlbut</option>
<option value="Lionel">Lionel</option>
<option value="Matthews">Matthews</option>
<option value="Mower">Mower</option>
<option value="Pennypacker">Pennypacker</option>
<option value="Stoughton">Stoughton</option>
<option value="Straus">Straus</option>
<option value="Thayer">Thayer</option>
<option value="Weld">Weld</option>
<option value="Wigglesworth">Wigglesworth</option>
</select>
<br/>
<input type="submit" value="Register"/>
</form>
</body>
</html>
As before, we have <head> and <body> tags. Within the <body>, theres
a <form> whose action attribute is register0.php. We see <input> tags
with typeset to "text," "checkbox," and "radio." Text and checkbox
should be self-explanatory, but radio refers to the bulleted buttons for
which the user can only choose 1 option. To create a dropdown menu,
we use the <select> tag with <option> tags within it. Finally we have our
submit button which displays "Register" as itsvalue attribute.
When we enter in values into this form and click "Register," were taken
to the register0.php URL that was specified in the action attribute of the
form. Unlike with our CS50 Search example, this URL doesnt have any
of our inputs embedded in it. Thats because we used the POST method
of sending data rather than the GET method.
conditions-1.php
To get a feel for this new language, lets take a look at how we would
implement conditions-1.c in PHP:
<?php
/**
* conditions-1.php
*
* David J. Malan
* [email protected]
*
* Tells user if his or her input is positive, zero, or negative.
*
* Demonstrates use of if-else construct.
*/
// ask user for an integer
$n = readline("I'd like an integer please: ");
// analyze user's input
if ($n > 0)
{
printf("You picked a positive number!\n");
}
else if ($n == 0)
{
printf("You picked zero!\n");
}
else
{
printf("You picked a negative number!\n");
}
?>
The syntax for PHP is actually quite similar to that of C. Variable names
in PHP are prefixed with a $. Variables also do not need to be declared
with explicit types because PHP is a loosely typed language. In different
contexts, PHP will implicitly cast variables from one type to
another. readline is a new function, but the if-else construct is identical
to C.
register-0.php
register0.php
<!DOCTYPE html>
<html>
<head>
<title>Frosh IMs</title>
</head>
<body>
<pre>
<?php print_r($_POST); ?>
</pre>
</body>
</html>
Within the <pre> HTML tags, we enter PHP mode by inserting the <?
php and ?>. Once were in PHP mode, we access a variable named $_POST.
This is an associative array which PHP constructs for you whenever you
pass in data via the POST method. If we had used the GET method, the
data would be available in the$_GET variable. print_r is a function which
prints recursively, meaning it prints everything thats nested within a
variable. When we pass the $_POST variable toprint_r, we see the four
inputs that the user provided, each with a key that corresponds to the
name
attribute
of
the
input. $_POST and $_GET are
known
assuperglobal variables because theyre available everywhere.
register-3.php
<?php
/**
* register-3.php
*
* Computer Science 50
* David J. Malan
*
* Implements a registration
registration
form
for
Frosh
IMs.
Reports
* via email.
*/
// require PHPMailer
require("PHPMailer/class.phpmailer.php");
// validate submission
if (!empty($_POST["name"])
empty($_POST["dorm"]))
{
// instantiate mailer
$mail = new PHPMailer();
&&
!empty($_POST["gender"])
&&
// use SMTP
$mail->IsSMTP();
$mail->Host = "smtp.fas.harvard.edu";
// set From:
$mail->SetFrom("[email protected]");
// set To:
$mail->AddAddress("[email protected]");
// set Subject:
$mail->Subject = "registration";
// set body
$mail->Body =
"This person just registered:\n\n" .
"Name: " . $_POST["name"] . "\n" .
"Captain: " . $_POST["captain"] . "\n" .
"Gender: " . $_POST["gender"] . "\n" .
"Dorm: " . $_POST["dorm"];
// send mail
if ($mail->Send() == false)
{
die($mail->ErrInfo);
}
}
else
{
header("Location: http://localhost/src9m/froshims/froshims-
3.php");
exit;
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Frosh IMs</title>
</head>
<body>
You are registered! (Really.)
</body>
</html>
$_SERVER
$_COOKIE
$_SESSION
Model-view-controller
As you begin to design web applications, youll want to think about how
to organize your code. One paradigm for organizing code is called
Model-view-controller (MVC). The View encapsulates the aesthetics of
the website. The Model handles interactions with the database. The
Controller handles user requests, passing data to and from the Model
and View as needed.
Lets try to create a course website for CS50 using the MVC framework.
In version 0, the pages are well organized into separate directories, but
there are a lot of files with very similar code.
To see how we might abstract away some of the logic, lets jump ahead to
version 5:
Now the header and footer are being automatically generated by the
function render. This is better design because we can change the header
and footer of all the pages within our site just by changing a few lines of
code.
Teaser
Soon well dive into the world of databases and even implement our own
e-trading website!
Last updated 2013-11-07 00:27:04 PST
Week 9, continued
Andrew Sellergren
Table of Contents
From Last Time
Reimplementing speller
Sessions and Cookies
counter.php
SQL
Race Conditions
JavaScript
dom-0.html
dom-2.html
Ajax
Teaser
From Last Time
We talked about how web servers send web pages to your browser when
they request it. Those web pages are written in a markup language called
HTML. CSS is a language that controls aesthetics like font size and
color. Ideally, for the sake of reusability and organization, CSS lives in a
separate file that gets linked into the HTML. One downside of this
approach is that it requires a separate HTTP request to be fetched and
thus it might increase latency. Smart browsers will often save a copy of
CSS files and other static content locally so that they dont have to be
refetched. This local repository is called a cache and the act of saving to
it is called caching.
Unlike HTML, PHP is a true programming language in that it can
express logic. When we need our web pages to change their content
dynamically, we use PHP to output HTML that the browser then
<?php
$size = 0;
$table = [];
function load($dictionary)
{
global $size, $table;
foreach (file($dictionary) as $word)
{
$table[chop($word)] = true;
$size++;
}
return true;
}
?>
<?php
$size = 0;
$table = [];
function check($word)
{
if (isset($table[strtolower($word)]))
{
return true;
}
else
{
return false;
}
}
function load($dictionary)
{
global $size, $table;
foreach (file($dictionary) as $word)
{
$table[chop($word)] = true;
$size++;
}
return true;
}
?>
All we need to check is if the index for a particular word is set to know
whether its in the dictionary. size and unload are similarly trivial to
implement:
<?php
$size = 0;
$table = [];
function check($word)
{
global $table;
if (isset($table[strtolower($word)]))
{
return true;
}
else
{
return false;
}
}
function load($dictionary)
{
global $size, $table;
foreach (file($dictionary) as $word)
{
$table[chop($word)] = true;
$size++;
}
return true;
}
function size()
{
global $size;
return $size;
}
function unload()
{
return true;
}
?>
$_COOKIE
$_GET
$_POST
$_SERVER
$_SESSION
<?php
// enable sessions
session_start();
// check counter
if (isset($_SESSION["counter"]))
{
$counter = $_SESSION["counter"];
}
else
{
$counter = 0;
}
// increment counter
$_SESSION["counter"] = $counter + 1;
?>
<!DOCTYPE html>
<html>
<head>
<title>counter</title>
</head>
<body>
You have visited this site <?= $counter ?> time(s).
</body>
</html>
Note that $counter exists in scope even outside the curly braces of the ifelse blocks.
Question: are PHP variables always global? Yes, unless they are declared
within a function.
The <?= $counter ?> syntax is shorthand for switching into PHP mode
and printing out a variable.
If cookies are used to identify users, then impersonating a user is as easy
as stealing a cookie. The defense against this is to encrypt HTTP headers
using SSL. This might be familiar to you as HTTPS. Even SSL
encryption can be broken though!
SQL
SELECT
INSERT
UPDATE
DELETE
For Problem Set 7, weve set you up with a database and an application
named phpMyAdmin (not affiliated with PHP) to interact with it. In that
database, there is a users table with id, username, and hash columns:
CHAR
VARCHAR
INT
BIGINT
DECIMAL
DATETIME
PRIMARY
INDEX
UNIQUE
FULLTEXT
Race Conditions
START TRANSACTION;
UPDATE account SET balance = balance - 1000 WHERE number = 2;
UPDATE account SET balance = balance + 1000 WHERE number = 1;
COMMIT;
Because these two UPDATE statements are part of the same transaction,
they will only succeed if both succeed.
JavaScript
Note that we dont have the $ prefix for variables anymore. We still dont
specify a type for the variable, though.
Another built-in data structure in JavaScript are objects:
<!DOCTYPE html>
<html>
<head>
<script>
+ '!');
function greet()
{
alert('hello, ' + document.getElementById('name').value
}
</script>
<title>dom-0</title>
</head>
<body>
<form id="demo" onsubmit="greet(); return false;">
<input id="name" placeholder="Name" type="text"/>
<input type="submit"/>
</form>
</body>
</html>
is getElementById which
specified id attribute.
retrieves
an
HTML
element
with
the
dom-2.html
<!DOCTYPE html>
<html>
<head>
<script
src="http://code.jquery.com/jquery-
latest.min.js"></script>
<script>
$(document).ready(function() {
$('#demo').submit(function(event) {
alert('hello, ' + $('#name').val() + '!');
event.preventDefault();
});
});
</script>
<title>dom-2</title>
</head>
<body>
<form id="demo">
<input id="name" placeholder="Name" type="text"/>
<input type="submit"/>
</form>
</body>
</html>
For now well wave our hands at the first line of JavaScript above.
Basically, it just waits till the document has loaded before executing
anything. $('#demo') is equivalent to document.getElementById('demo').
One feature of JavaScript that were leveraging here is the ability to pass
functions as objects to other functions. The only argument to
the submit method is ananonymous function that takes in its own
argument event.
Ajax
ajax-2.html
<!-ajax-2.html
Gets stock quote from quote.php via Ajax with jQuery, embedding result
in page itself.
David J. Malan
[email protected]
-->
<!DOCTYPE html>
<html>
<head>
latest.min.js"></script>
<script>
<script
src="http://code.jquery.com/jquery-
/**
* Gets a quote via JSON.
*/
function quote()
{
var url = 'quote.php?symbol=' + $('#symbol').val();
$.getJSON(url, function(data) {
$('#price').html(data.price);
});
}
</script>
<title>ajax-2</title>
</head>
<body>
Week 10
Andrew Sellergren
Table of Contents
Announcements and Demos
Technology as Owned vs. Unowned
Unowned Technologies
Hourglass Architecture
Within two years, the first digital spreadsheet (VisiCalc) was developed
by Bob Frankston and Dan Bricklin and the Apple II started flying off
the shelves
The Apple II was unowned in the sense that it accepted contributions
from others and if something went wrong with it, it wasnt clear that it
was Apples fault; this model became the model for all that followed
Its not up to you, for example, to put the 7th blade in your razor!
Unowned Technologies
category of actions for users, you had to petition them to put it on the
home screen.
Enter ARPANET, the predecessor to the Internet as we know it. Its
founders didnt expect to make any money off it and didnt have the
resources to roll it out everywhere. IBM even said in 1992 that it would
be impossible to build a corporate network using TCP/IP.
Think of packets on the Internet like beer in Fenway Park. It can get
almost all the way from the vendor to the buyer, but the last distance it
travels is through a number of other spectators. They have no real
contract with the buyer, but they pass it along anyway. Similarly, there
are entities on the Internet that handle packets properly despite having
no relationship with the sender or the receiver.
Hourglass Architecture
This is also a lesson in trust and security. Malicious software has only
gotten more sophisticated, beginning with the likes of the Storm Worm
and progressing to the hardly detectable Stuxnet.
An amusing anecdote: the Capn Crunch Bosun whistle emitted a ton at
the exact frequency that AT&T recognized as an idle line. If you blew it
into the telephone receiver, you could get free long distance! Because
theirs was an owned technology, AT&T could quickly fix this
vulnerability. Vulnerabilities in unowned technologies, for example
viruses, malicious links, or even remote access tools (RATs), cannot be
so easily fixed.
Technology as Hierarchy vs. Polyarchy
As a CS50 grad, who are you in this riddle? You have a tool with which
you can change everything. Use it to forge systems that distribute power
rather than focus it.
Last updated 2013-11-14 01:12:38 PST
Week 10, continued
Andrew Sellergren
Table of Contents
Announcements and Demos
From Last Time
Final Project
Web Hosting
Security
Session Hijacking
Man in the Middle
SQL Injection Attack
In case you havent seen it yet, check out the story of Saroo, the boy
who overslept on a train and found his family 25 years later using
Google Maps.
We know from collecting statistics that about 50% of you wont continue
on to take any more CS courses. Thats okay! One of our overarching
goals is to empower you to understand the world of technology as you
pursue other knowledge. Bring your ideas and your Final Projects to
other departments!
earth.getLayerRoot().enableLayerById(earth.LAYER_BUILDINGS, false);
Final Project
Just to plant one seed in your mind, check out the list of e-mail
addresses for the various cell providers here. With these, you can send
text messages programmatically! Be careful, lest you send some 20,000
text messages mistakenly, as David did during lecture last year.
Receiving text messages is a little more difficult. You can use the service
provided by textmarks.com. For example, if you send a text message to
41411 like "SBOY mather quad," youll get a response from the CS50
Shuttleboy app.
Consider using Parse as your backend database instead of MySQL!
CS50 has its own authentication service called CS50 ID. Check out
the manual to see how to verify that a user is someone from Harvard.
Web Hosting
Check out your options for web hosting if you want your Final Project to
live outside of the Appliance. Namecheap is just one!
To see who owns a particular domain, you can look it up
using whois from the command line. Under the "Name Servers" heading,
youll see a list of servers that are the canonical sources for returning the
IP address of the domain you looked up. When you type in this domain
into your browser, the browser will eventually query these name servers
to find the final IP address. When you register for web hosting, youll
need to tell the registrar what your name servers are. Since CS50 uses
DreamHost,
youll
enter
in
NS1.DREAMHOST.COM,
NS2.DREAMHOST.COM, and NS3.DREAMHOST.COM if you use
CS50s hosting account.
SSL stands for secure sockets layer and is indicated by a URL that
begins with https. To use SSL for your own website, you need a unique
IP address for which youll have to pay a web hosting company a few
more dollars per month.
Security
As a random segue into security, check out the first volume of CS50
Flights.
As we talked about on Monday, its important to be careful when
installing software. Often youll be prompted to give permission to an
installer as a security measure because it needs to run as an
administrator. This has very serious security implications because youre
giving this installer the ability to execute almost any command on your
computer.
The trust you implicitly or explicitly give to the software you run can
easily be abused. Sony got a lot of flak a few years ago for including
rootkits on the CDs they sold. These rootkits would actually hide
themselves so that you couldnt see they were running if you opened
Task Manager.
What does the padlock icon on a website mean in terms of security?
Virtually nothing. But weve been conditioned to think that a website is
secure when we see that padlock. That means its just as easy for an
adversary to put a padlock on his malicious website and trick you into
trusting him.
Some browsers like Chrome go one step further in showing the owner of
the SSL certificate. When you navigate to Bank of Americas website,
Chrome shows "Bank of America Corporation [US]" in green in the
address bar.
But how many of you have actually noticed or changed your behavior
because of these security measures?
Session Hijacking
You can see the actual value of the cookie that Facebook plants on your
computer by using Developer Tools in Chrome. Usually this cookie is
planted when you first visit Facebook. But how did you get to Facebook?
You probably didnt type "https" to begin with, so you must have be
redirected to the SSL-enabled version of the website. During that
redirection, your cookie was forwarded along. If a bad guy is on the
same network on you, he may be able to intercept this cookie while
youre being redirected. This attack is called session hijacking.
Man in the Middle
A bad guy could even intercept your HTTP request and respond with his
own fake version of Facebook in order to steal your credentials. This
attack is called man in the middle.
SQL Injection Attack
$username = $_POST["username"];
$password = $_POST["password"];
query("SELECT
*
FROM
users
password='$password'");
WHERE
username='$username'
AND
This looks reasonable and correct, but its vulnerable to a SQL injection
attack. What if the user enters skroob as his username and 12345' OR '1'
= '1 as his password. Now the query you execute looks like the
following:
SELECT * FROM users WHERE username='skroob' AND password='12345' OR '1'
= '1'
Because 1 = 1, this query will always return a row even if 12345 is not the
correct password for username skroob.
In Problem Set 7, we asked you to use question marks as placeholders
for user input when executing the query function. One thing
the query function does with these question marks is guarantee that user
input will be properly escaped.
Last updated 2013-11-15 20:23:57 PST
Week 12
Andrew Sellergren
Table of Contents
Announcements and Demos
This is CS50 Jeopardy!
Farewell
Announcements and Demos
what ultimately matters in this course is not so much where you end up
relative to your classmates but where you, in Week 12, end up relative to
yourself in Week 0
73% of you had no prior CS experience before joining this course!
Congratulations on no longer being part of "those less comfortable" if
you once were!
Can you guess what the significance of the following numbers from Quiz
1 is?
34 59 20 106 36 52
Code::Blocks
cygwin
Eclipse
NetBeans
Visual Studio
XAMPP
o Linux
CS50 Appliance
clang
Code::Blocks
Eclipse
NetBeans
XAMPP
o Congrats to Richard for topping the Big Board from Problem Set
6! Also happy birthday to Mike! Also congrats to Chris, Layla,
Raul, Daniel, and Daniel for identifying and taking pictures with
so many computer scientists from Problem Set 5!
A special thank you to Joseph, R.J., and Lucas, our head teaching
fellows; to Zamyla, our fearless walkthrough leader; to Rob, our
preceptor; and to Lauren, our course head for making CS50 possible!
And, of course, thank you to all 102 members of the staff! Want to be a
part of the fun next year? Apply now!
Wed also like to applaud Gabriel for his efforts in bringing CS50 to his
high school in Brazil, there known as CC50. He started teaching CC50 as
a 17-year-old back in 2011 and now hes a teaching fellow of our own!
The Hackathon will be held on 12/4! Its an opportunity for you to work
independently or with your partner(s) to finish up your Final Project.
Dont wait till next Wednesday to start coding!
The CS50 Fair is on 12/9! There youll have a chance to show off your
Final Project to friends and family as well as mingle with friends from
industry including:
o Amazon Web Services
o APT
o Box
o Bridgewater
o eBay
o Facebook
o Google
o Kayak
o Microsoft
o Quora
This is CS50 Jeopardy!
Those review questions you were asked to submit as part of the last
problem set have now become part of a game show!
Question: how is C typed? Answer: strongly!
Question: what is the answer to life, the universe, and everything (in
binary)? Answer: 101010.
Question: what does the fox say? Answer: ring ding ding ding ding ding
ding.
Question: what used to allow you to make free long distance phone
calls? Answer: the Captain Crunch whistle!
Question: what does the status code 403 mean? Answer: Forbidden.
Farewell
It has been a pleasure teaching you this semester! We leave you with a
reel of outtakes.
Last updated 2013-11-27 20:07:40 PST