Home | Learning Goals | Schedule | Administration | Readings | Lab/Lecture Notes | Assignments (Theory/Programming) | Computing |
A list of tricks and notes that might help with labs.
Some items are tagged to denote context. For example, [C++11] means you need C++11 enabled to use the feature described.
-Wall
and -o
do?)To run a program in the terminal so you can type other commands while it's running you can add &
at the end.
For example, you can type this without closing gedit:
gedit file.cpp &
g++ -Wall file.cpp -o file
For a more elaborate tutorial, look here.
-Wall
and -o
do?) Disambiguation: command-line arguments, options and flags all mean the same thing.
For those who haven't used UNIX-like systems before, here is the structure of a command typed at the command line:
PROGRAM_NAME FILE_OR_OPTION...
where a file is just the file name and an option starts with a dash (-). An option may have one or more values associated with it (you just write them after it separated by a space). Files and options are separated by spaces.
For example, g++ -Wall file.cpp -o file
means this:
g++
is the name of the program we want to run-Wall
is an option (starts with -, has no value)file.cpp
is a file passed to g++
-o
is another optionfile
is a value given to -o
So, what do -Wall
and -o file
mean? This depends on the program. In this case, g++ understands -Wall
as "enable all warnings" and -o file
as "the name of the output should be 'file'". Why does -Wall
not take file.cpp
as argument like -o
takes file
? Because -Wall
happens not take arguments, so file.cpp
just gets interpreted on its own.
If you don't know what options a program accepts or what some option means, try YOUR_PROGRAM -h
or YOUR_PROGRAM --help
. It is a convention that this will show you some text describing how to use that program. Try it on g++ and see what happens!
-h
to find out how to use them):cp
: copies filesmv
: works like cut and paste for filescat
: tells you what's in a filecd
: changes the current directory/folder you're inls
: tells you what files are in the current folderThe experience of using gdb can be improved significantly by typing the following command at the gdb prompt:
tui enable
This will split the terminal into 2 panes, one showing the source code (highlighting the line you are on) and one showing the gdb prompt. You can revert this by typing gdb disable
.
Still don't like gdb? Try kdbg - it has a GUI (just type kdbg
into the command prompt with X forwarding enabled, it should just work).
Pointer semantics are a way of thinking about pointers that might help you understand when you need to call delete
.
There are 3 categories.
You should not delete a non-owning pointer. A non-owning pointer looks like this:
int x;
int* y = &x;
y
points to x
, but you did not allocate x
. This also applies to function arguments:
void fn(int* z){
*x = 3;
}
int x;
fn(&x);
Same reasoning - you did not allocate x
and no one else allocated it for you, so &x
and z
non-owning pointers. You should not delete it.
Owning pointers are pointers you should delete. You either allocated the memory they point to yourself or someone has done did it for you (and then "transferred ownership" to you).
Examples:
int* a = new int(3); // you allocated it yourself
// so a is owning
int* b = a; // is this owning? if so, a is now non-owning.
// there can be only one owning pointer.
// if not then b must be non-owning.
int* fn2(){
int* x = new int(5); // clearly an owning pointer
// (see above)
return x; // when you return it now the caller owns it
}
int* c = fn2(); // c is an owning pointer since fn2 returns
// an owning pointer
In a singly linked list, you can think of the head owning the first node, with each node owning its next node.
What about data structures where you have more than one pointer to a thing? (i.e dequeue from lab 2) If you know you should delete only one specific pointer, then that one is owning and all others are non-owning.
For dequeue, there is a single-owner model for pointers... can you find it?
But what about when you really could delete one of several pointers depending on circumstances? Maybe the last pointer to be null is not always the same? Then you have to get creative... this is not really covered in this course, but it involves either reference counting or garbage collection.
Make, or GNU Make is an interesting program. It's very powerful if you have the patience to learn about it.
When you type make
, the program will look in your current directory and try to read a file called Makefile
(exactly that, don't add .txt or anything).
Make has various "targets" defined in this file - the most common ones are "all" and "clean".
Notice also how running make twice does nothing - it checks if your source files are more recent than your output files and only builds then. To force it to rebuild either touch source.cc
or make clean
. touch
makes the source file more recent, as in "pretend I just changed and saved this", and make clean
remove the output files, forcing make to recreate them.
The above should get you started with any project file we give you. What comes next is pointers for if you want to know how this actually works.
The format of a Makefile looks something like this:
VAR = values...
target: dependencies...
g++ etc...
echo "shell commands go here"
echo $(VAR) # use variables like this
Let us say you have an example C++ file hello.cpp
:
#include <iostream>
int main(){
std::cout << "Hello World!\n";
return 0;
}
You could just compile this with g++
, but where's the fun in that?
Here's how you do this with make
:
hello: hello.cpp
Type make
and it should compile. This is (mostly) equivalent to this Makefile:
hello: hello.cpp
$(COMPILE.cpp) hello.cpp -o hello
Which is (usually) equivalent to:
hello: hello.cpp
g++ hello.cpp -o hello
For why this is, take a look here.
Extra note, for systems with multiple files try this (compile and link foo.cc
, bar.cc
into program):
# reminds make to link the C++ standard library
# into the final executable
%: %.o
$(LINK.cc) $^ -o $@
# make ~knows~ that foo.o must come from foo.cc, etc...
program: foo.o bar.o
You can have a lot of fun with variables too. Most builds would not use variables very much, so to distill some useful concepts here is a ridiculous Makefile that tries to do math.
For the rest of this section, we do not ask why, but rather why not?
# if A of B are not provided, set them
A ?= 2
B ?= 4
# C is set
# $(shell x) runs x in the shell
C := $(shell expr $(A) '*' $(B))
D = $(shell expr $(A) '*' $(B))
add:
@echo "A+B=$(shell expr $(A) + $(B))"
# alter the value of B to show the difference between
# = and :=
multiply: B=10
multiply:
@echo "C=$(C)"
@echo "D=$(D)"
Run the file like this:
A=5 B=20 make add
This should print out 25. To use the defaults, type make add
. This will output 6. To call multiply instead: A=4 B=8 make multiply
.
Keywords for further reading:
By a convention from long ago in a galaxy far away in the late 20th century, main functions in C or C++ can take int argc, char** argv
. This is like String[] argv
from Java or sys.argv
from Python, but done with pointers.
argc
is the number of arguments passed to the command, including the command's name.argv
is a pointer to an array of length argc of pointers pointing to c-strings that represent each part of the command used to launch the program.For example, if your program is launched with the command ./program 1 2 "a b c" potato
then argc
will be 5 and argv will point to the series of c-strings: "1", "2", "a b c", "potato".
Further discussion: many more thoughts and explanations on stackoverflow.
Pointers and references can be a bit mind-bending. Here's a worksheet you can use to build confidence in working with them.
#include <iostream>
int main(){
int x = 2;
int y = 4;
int* a = &x;
int* b = nullptr;
int*& c = b;
int** d = &a;
// this defines a function that prints out all our variables (for convenience)
auto print_values = [&](){
std::cout << "Values:\n=======\n";
std::cout << "x=" << x << " y=" << y << "\n";
std::cout << "a=" << a << " b=" << b << "\n";
//line
if(a){
std::cout << "*a=" << *a << " ";
}
if(b){
std::cout << "*b=" << *b;
}
std::cout << "\n";
//line
std::cout << "c=" << c;
if(c){
std::cout << " *c=" << *c;
}
std::cout << "\n";
//line
std::cout << "d=" << d;
if(d){
std::cout << " *d=" << *d;
if(*d){
std::cout << " **d=" << **d;
}
}
std::cout << "\n";
};
print_values();
c = a;
print_values();
b = &y;
print_values();
c = nullptr;
print_values();
a = nullptr;
print_values();
d = &c;
print_values();
b = &x;
print_values();
return 0;
}
Read through this and predict the values it will print out, then run it and check. Drawing a table might help.
Note: it requires c++11 to compile.
On the lab machines, we have gcc 4.8.5
(as of last update to this web page). To compile using C++11 features, you can use the --std=c++11
flag.
For example, this is how you would compile a file called test.cpp
:
g++ test.cpp -Wall --std=c++11 -o test
You've probably already seen this code in lecture slides:
int data[4] = {1,2,3,4};
for(int i = 0; i < 4; ++i){
std::cout << i << "\n";
}
Sometimes you need a fully written out for loop with conditions and such, but other times you can write this more simply:
int data[] = {1,2,3,4};
for(int i: data){
std::cout << i << "\n";
}
Now you don't have to worry about messing up the loop condition and increment, and it's shorter to type. It reads like "for each i in data, do this".
Read this for exactly what this does and how it works.
January 7 2017