CPSC 221: Basic Algorithms and Data Structures
2016 Winter Term 2 (January-April 2017)

Lab FAQ and tricks


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.

Contents:

You don't have to close gedit to run g++

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.

Command-line arguments (what do -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:

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!

Some basic programs you should know about (use -h to find out how to use them):

Dealing with frustrating debugging experiences

The 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 (AKA when to delete)

Pointer semantics are a way of thinking about pointers that might help you understand when you need to call delete.

There are 3 categories.

Non-owning

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

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.

It's complicated (shared ownership)

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.

What does make do?

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.

Build an executable

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

Do math

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:

argc and argv

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.

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

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.

How to enable C++11

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

[C++11] Range-for (another way to iterate)

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.

 

Last Modified:

January 7 2017