wget www.labs.cs.uregina.ca/330/CompileDebug/Lab2.zip unzip Lab2.zip
Although you can do a lot with a graphical user interface (GUI), it is often necessary to execute commands on the command-line. The basic format is:
prompt$ command-name options arguments
For example:
titan[1]% g++ -c main.cpp
a037094[7]% ls -l
A summary of a few commands is provided in tabular format here:
Command | Example | Comments |
---|---|---|
ssh | ssh smithj@titan.cs.uregina.ca | Secure Shell (ssh) enables you to log in, execute commands, and run applications on a remote system. SSH encrypts any communication between the remote user and a system on your network. |
scp | scp input.txt smithj@titan.cs.uregina.ca:CS330lab1/ scp -r CS330 smithj@titan.cs.uregina.ca:classes/ |
With Secure Copy (scp) you can copy files between the remote host and a network host. scp actually uses ssh to transfer data and employs the same authentication and encryption.
This first example copies the file input.txt from a users current directory to the user smithj's CS330lab1 directory, located on the cs managed host. |
touch | touch myfile | Update the access and modification time of "myfile". If "myfile" does not exist, create it. |
mkdir | mkdir reports | Creates a directory named "reports" |
rmdir | rmdir letters | Erases a directory named "letters" |
rm | rm myfile rm -r mydir |
Erases a file named "myfile" Erases the directory "mydir" and any contents/subdirectories in it |
ls | ls -F
ls -a ls -l ls -i ls -t |
Lists working directory with trailing characters for file types. Most common are / for directory and * for executable.
Lists all files including "hidden files" Lists files with permissions, owner, group, time stamp List the files inode number--a unique number used by the system to identify a specific file. List files by time last modified. |
cd | cd reports cd cd .. |
Changes to the "reports" directory, making it the working directory. Changes back to the home directory Moves you up one directory level |
pwd | pwd | Print Working Directory - prints full path to current directory. |
cp | cp lab1 mylab cp lab1 mydirectory cp lab1 mydirectory/mylab cp -r mydirectory dirname |
Copies file "lab1" to "mylab" file Copies "lab1" in your working directory to "mydirectory" Copies "lab1" to "mydirectory" and renames it "mylab". Copies "mydirectory" and all its contents into "dirname". |
mv | mv lab1 lab2 mv lab1 labdirectory mv lab1 labdirectory/newfile mv labdirectory newdirectory |
Renames "lab1" to "lab2" Moves "lab1" to the "labdirectory" Moves "lab1" to the "labdirectory" and renames it "newfile" Renames a whole directory to a new directory name |
g++ or CC (on Solaris machines) |
g++ -o prog_run main.cpp g++ -o prog_run main.o part1.o part2.o |
compiles and links "main.cpp", calls the executable "prog_run" compiles "main.cpp" (creates the object file) links the object files (when you have different files), calls the executable "prog_run" |
For another summary of Unix commands, click here.
For a Unix tutorial, click here.
Before we get into a discussion of make, it would be good to review how to compile projects on Linux. Given three files: main.cpp, greet.cpp, and greet.h, we must undergo two steps to create the executable file "demo".
A044872[102]% g++ -c greet.cpp A044872[103]% g++ -c main.cpp A044872[104]% g++ -o demo greet.o main.o A044872[105]% demo
Hello, World
Note that after issuing the g++ -c greet.cpp command, the object file greet.o will be produced.
Note: some content on make is copied from the 170 lab |
The idea behind make is that it simplifies the compilation of projects with multiple files. Consider the above example (with two .cpp files). Compared to a single file project, you must issue two additional commands. Now, think about what happens when your project consists of several more files. You will waste a lot of time typing.
This is where make comes in handy. It automates compilation. It checks which files have been modified and based on dependencies (or rules) determines which object files will need to be recompiled. In addition, it saves time by compiling only files that have changed since the last build.
make is a UNIX command that looks for a file called Makefile or makefile. (Makefile is typically preferred because it appears at the beginning of the directory listing). Within the Makefile, there are variables and things called dependencies. A simple make file for the project that we discussed above might look like this:
The red text highlights the explanations.
# leads comments in a line # Build all: default target all : demo # Separate compilation to build object files main.o : main.cpp greet.h g++ -c -ggdb main.cpp greet.o : greet.cpp greet.h g++ -c -ggdb greet.cpp # linking #demo is a target which depends upon main.o and greet.o #"g++ main.o greet.o -o demo" is the command to produce the executable file #You need to use a TAB before g++ demo : main.o greet.o g++ main.o greet.o -o demo # Testing check : all ./demo # Clean up all build targets so that one may get a clean build clean : rm -f *.o demo
Information on how to use the make command:
main.cpp is the main program greet.cpp and greet.h contain the helper function Makefile contains the build script "make all" or simply "make" to build everything "make clean" to erase all the files built by make "make clean all" to get a clean build "make check" to run the "demo"
Makefile(s) may also contain variables. For instance, if you will be adding additional object files or changing the compiler, it will be easier to use variables and make modifications only in one place in the file. variables are set using the equal sign as in
CXX=g++Note that by long standing convention, the variable CC is used to define the C compiler to be used. The variable CXX was added to this convention to define the C++ compiler.
To use the variable, it is prefixed by a $ and surrounded by parenthesis as
in: $(CXX)
. A Makefile with variables might look something
like the following:
# this is a comment # specify the object files ... OBJ= global.o access.o mem.o rungoal.o sup.o unify.o wexhdr.o # specify the compiler CC = cc # this is the cross platform standard C compiler CXX = g++ # this is the GNU C++ compiler #CXX = CC # Solaris C++ compiler # specify the compiler options CFLAGS = -g # specify compiler preprocessor options CPPFLAGS = -I/usr/local/include # specify linker options LDFLAGS = -L/usr/local/lib # specify the name of the ultimate executable file EXEC = runwex # create the executable $(EXEC): $(OBJ) $(CXX) $(LDFLAGS) -o $(EXEC) $(OBJ) @echo 'runwex has been created' access.o: access.cpp global.cpp globdefs.h allwexhdr.h $(CXX) $(CPPFLAGS) $(CFLAGS) -c access.cpp global.o: global.cpp global.h globdefs.h $(CXX) $(CPPFLAGS) $(CFLAGS) -c global.cpp mem.o: mem.cpp $(CXX) $(CPPFLAGS) $(CFLAGS) -c mem.cpp rungoal.o:rungoal.cpp rungoal.h global.h sup.h $(CXX) $(CPPFLAGS) $(CFLAGS) -c rungoal.cpp sup.o: sup.cpp sup.h $(CXX) $(CPPFLAGS) $(CFLAGS) -c sup.cpp unify.o: unify.cpp globdefs.h $(CXX) $(CPPFLAGS) $(CFLAGS) -c unify.cpp wexhdr.o: wexhdr.cpp $(CXX) $(CPPFLAGS) $(CFLAGS) -c wexhdr.cpp clean: -/bin/rm -f $(EXEC) $(OBJ)
Some comments on this Makefile:
You may have noticed the "@echo" command in the makefile. You can use this to direct, for example, informational or status messages to the console.
Some of the comments on this page are specific to the GNU make. The Linux systems only use the GNU version, which can be accessed using either the make command or the gmake command.
Like most UNIX commands, make has a number of command line options. A few of them are as follows:
You can try them out if you like!
Note: most of the following content on gdb is copied from Guili liu's 170 lab |
Let's say that your code compiled without any errors (using make, of course), but now you run the program and it either:
What can you do? Well, one option is to put print statements to watch where the code has gone. A more sophisticated method, however, is to use a debugger. This is where gdb enters the picture.
gdb is GNU debugger. You can use the debugger to control the running of the error-prone program and examine variables when/where problems arise. The most popular debugger for UNIX systems is gdb. It has tons of features, but you only need to use a few to get started.
When you execute a program that does not behave as you expected, you need some way to step through the logic other than just looking at your code. Things that you might want to know may be the following:
g++ -g filename.cpp -o executablefileUnder this -g or -ggdb option, g++ creates additional information about the program and deposits it in a symbol table. The debugger must have this symbol table to do its work. You will start gdb by typing in the following command:
gdb executablefilewhere executablefile is the executable version of the program. If you did not use -o option when you compile the program, the executablefile will be a.out.
gdb will give you a prompt that looks like this:(gdb). From this prompt you can use gdb commands such as run or list and so on. To exit the gdb program, just type quit at the (gdb) prompt and then press the enter key.
run 2048 24 4
You can even do input/output redirection: run > outfile.txt.
Just list without any arguments will print out the lines just after the lines that you printed out with the previous list command.
A breakpoint is a spot in your program where you would like to temporarily stop execution in order to check the values of variables, or to try to find out where the program is crashing, etc.
break function sets the breakpoint at the beginning of function. If your code is in multiple files, you might need to specify filename:function.
Break linenumber or break filename:linenumber sets the breakpoint to the given line number in the source file. Execution will stop before that line has been executed.
step executes the current source line, and then stops execution again before the next source line with available debug information.
#include <iostream> using namespace std; int DivideInt(int, int); int main() { int x = 5, y= 2; cout << " x / y = " << DivideInt(x, y) << endl; x = 3; y = 0; cout << " x / y = " << DivideInt(x, y) << endl; return 0; } int DivideInt(int a, int b) { return a / b; }To use gdb on this program, you must use -g or -ggdb option when you compile the program.
g++ -g sample.cpp -o sampleThis is what a compile and run would look like:
A044876[7]% g++ -g sample.cpp -o sample A044876[8]% sample x / y = 2 Floating exception (core dumped)A core dump occurs when a program crashes. Core files are usually large (and take up valuable disk quota). It is is a good idea to remove them.
Now let's use gdb to find out the bug.
A044876[9]% gdb sample GNU gdb Red Hat Linux (5.3post-0.20021129.18rh) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public license, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-Linux-gnu"... (gdb) list 1 #include2 using namespace std; 3 4 int DivideInt(int, int); 5 6 int main() 7 { 8 int x = 5, y= 2; 9 10 cout << " x / y = " << DivideInt(x, y) << endl; (gdb) list 11 12 x = 3; 13 y = 0; 14 15 cout << " x / y = " << DivideInt(x, y) << endl; 16 17 return 0; 18 } 19 20 (gdb) list 21 int DivideInt(int a, int b) 22 { 23 return a / b; 24 } 25 (gdb) run Starting program: /home/hercules/t/temp1/gdb/sample x / y = 2 Program received signal SIGFPE, Arithmetic exception. 0x08048702 in DivideInt(int, int) (a=3, b=0) at sample.cpp:23 23 return a / b; (gdb) print a $1 = 3 (gdb) print b $2 = 0 (gdb) quit The program is running. Exit anyway? (y or n) y A044876[10]%
The above is only an example of using gdb. We encourage you to try it when you debug your class assignments. The more you practice, the more you will learn and the more comfortable you will feel with gdb
If you have logged onto Titan, you can then log onto os1 or os2 using the following command:
ssh os1 OR ssh os2
Reasons to use the os1 and os2 machines at home:
You can use a program installed on Linux called valgrind to check for memory leaks. It can tell you if you are:
Rather than re-inventing the wheel, you can get more information about valgrind from these places:
A sample use of valgrind (with options turned on to provide details about memory leaks) for your executable is:
valgrind --leak-check=yes ./executable
C++ is NOT C.
Here are some differences that might mess you up:
C++ Style | C Style |
---|---|
// function for passing by reference in C++ void swap (int& x, int& y) { int temp; temp = x; x = y; y = temp; return; } // end swap //In main, the call will look something like this swap(a,b); |
/* function for passing by reference using pointers */ void swap (int* x, int* y) { int temp; temp = *x; *x = *y; *y = temp; return; } /* end swap */ /* In main, the call will look something like this */ swap(&a,&b); |
C++ Style | C Style |
---|---|
int *i4; //create an array of 4 integers i4 = new int[4]; //... delete[] i4; |
int *ic4; /* create an array of 4 integers */ ic4 = (int *) malloc (sizeof (int) * 4); /* ... */ free(ic4); |
C++ Style | C Style |
---|---|
#include <iostream> using namespace std; int main() { int a; cout << "Please enter an integer: "; cin >> a; cout << "The number is: " << a << endl; return 0; } |
#include <stdio.h> int main() { int a; printf("Please enter an integer: "); scanf("%i", &a); printf("The number is: %i\n", a); return 0; } |
For more of these differences, see Dr. Hilderman's notes: Differences Between C and C++