Programming on the SRJC Student Server
Before you get started, you should read Basic Unix Commands and be comfortable using a command line. You may want to have taken or be enrolled in CIS 50.71. Other Unix/Linux resources which go into much greater detail include the Oslo College Unix compendium and Rute User's Tutorial and Exposition.
Read SSHing to student.santarosa.edu for instructions on connecting to the Student server. To practice programming in the command-line environment on your own computer: if you have MacOSX, you can access a Unix terminal through Applications/Utilities/Terminal. For Windows, MinGW and CygWin each provide a Unix-like environment, or the Dev-C++ IDE comes with gcc which can be run from the Windows cmd.exe shell.
Getting Started
Unlike CodeWarrior, you will be writing programs in individual text files, not "projects". Simple text editors on Student include nano and joe. You can also use the more complex editors vi or emacs .
You can also write programs on your own computer and upload them to Student to run them.
Compiling C and C++ Programs
This is not a guide to programming or to the C or C++ languages. For that, take CIS 10 or CIS 20. This document only explains how to compile and run programs you have already written on the Student server. The program executables ("binaries") will only run on Student or another Intel x86-based Linux or FreeBSD system.
The C/C++ compiler on Student is gcc, the GNU Compiler Collection (formerly the GNU C Compiler). You must give your files the correct extension (.c for C files, .cc or .cpp for C++) or else GCC will not know what to do with them. You run gcc by giving it your source code files and a name for the program you want to build:
# If you are programming in C, the command is "gcc"
gcc -g file1.c file2.c -o myProgram
# If you are programming in C++, the command is "g++"
g++ -g file1.cpp file2.cpp -o myProgram
The -g option tells GCC to put extra information in the program so that you can debug it. The -o option tells GCC what to name the program it produces. If you don't use -o, then GCC will create a program named "a.out".
After successfully compiling a program, you cannot run it by simply typing its name. This is because, unlike DOS and Windows, the current directory "." is not in the shell's $PATH command search path. This is a security consideration that became customary on Unix decades ago. Because of this, you will need to specify the relative path to the program "./" in order to run it.
username$ gcc -g someProgram.c -o someProgram # Compile the program
username$ ./someProgram # Run the program
Other gcc options you could use
To see more warnings so you write cleaner code, use the set of options "-Wall -ansi -pedantic". This turns on GCC's warnings (-Wall), enforces ANSI compatibility (-ansi), and makes GCC enforce ISO compatibility (-pedantic).
To keep from always having to type these options, you can set an alias for gcc in your ~/.bash_profile file that will be loaded every time you log in:
alias gcc="gcc -g -Wall --ansi --pedantic"
alias g++="g++ -g -Wall --ansi --pedantic"
If you have your own collection of header files and libraries, you can put their locations in GCC's header and library search path using -I for the directory containing your header files and -L for the directory containing your libraries.
# Note the escaped end-of-line for a multi-line Bash string
alias gcc="gcc -g -Wall \
-I/home/user/username/include \
-L/home/user/username/lib"
If you have to link in an external library, use the -l option followed by the part of the library's name after "lib", not including the ".so" at the end.
gcc foo.c -l m # Link to /usr/lib/libm.so (Math library)
gcc foo.c -lm # also works
# Link to math library, XML library, and Electric Fence:
gcc foo.c -lm -lxml2 -lefence
To define a preprocessor value from the command line, use the -D option:
# This is equivalent to #define DEBUGLEVEL 4
gcc foo.c -DDEBUGLEVEL=4 -o myProgram
# This is equivalent to #define FOO and #define BAR
gcc foo.c -DFOO -DBAR -o myProgram
Finally, if you're interested in optimizing your logic, you can use the -pg option to make your program output a data file when you run it. You can then run the gprof program in the same directory as your program to see what functions took the most time.
Too see all the options available for GCC, run man gcc from the command line. For more information about GCC, read the GCC manual.
How compilation works
Compilation of a program involves several steps:
| Step | Result | Option |
|---|---|---|
| Start with source and header files | *.c, *.cpp, *.h | N/A |
| Run pre-processor | *.i | -E |
| Compile to assembly | *.s | -S |
| Assemble to object code | *.o | -c |
| Link to libraries and build program | a.out |
Passing one of the options -E, -S, or -c causes gcc to end compilation early and create, respectively, a file of pre-processed source code, assembly code, or object code.
If you do not pass one of these options, gcc will just try to build the program and will not produce intermediary .i, .s, or .o files.
Handling command-line arguments
C (and hence C++) provides a simple method of taking arguments or options from the command line, by declaring main like this:
int main(int argc, char *argv[])
{
...
}
argc (argument count) is the number of arguments, counting from 1, counting your program name as an argument. If you ran ./a.out foo bar, argc would be 3.
argv[] is an array of strings (pointers-to-char) holding the arguments. It also counts your program name as an argument, so argv[0] is the name of your program as it was called. argv[1] is the first argument after that (or a bug if you didn't pass any), and argv[2] the next, and so on.
You can use argc and argv to create your own argument handling routine, but be careful to stay within the bounds of valid data when using argv. A good rule is that argc is the number of elements in argv, but if you change argc or argv, you have to take the change into account.
For easier argument handling, there are third party libraries like GetOpts.
Understanding gcc error messages
GCC error messages tell you what file name, function, and line number an error was detected in.
buggy.cpp: In function `int main()':
buggy.cpp:11: assignment of read-only location
If the error is not at the given line number, it is usually before it. The line number is merely the place where the compiler could tell that there was an error in the program.
Concentrate on fixing the first errors reported by the compiler before looking into later ones. Once a compiler runs into a syntax error, it gets confused and any further errors it reports may either be legitimate errors or may just result from the original error.
Here are some error messages you might run into:
- syntax error before '}' token
- Check your code above this line for a missing semicolon, brace, or other symbol.
- non-lvalue in assignment
- The left side of an assignment operator ('=') must be a variable, something that can store a value.
- undefined reference to `__gxx_personality_v0'
- You may be trying to compile a C++ program with gcc instead of g++. Try using g++ or linking -lstdc++ into the program.
- whatever.h: No such file or directory
- The compiler could not find the header file. Make sure the header file exists and that the #include path from the source file to the header is correct.
- `foo' undeclared
- A variable or function was not declared before it was used. Check that it was not misspelled and that you have included all the headers needed by your functions.
- redefinition of `class foo'
- A variable was declared twice. If this occurs in a header that is loaded by more than one of your source files, make sure your headers are wrapped in #ifndef FILENAME_H ... #define FILENAME_H ... #endif blocks.
- undefined reference to `main'
- You may have forgotten to create main() function. Every C and C++ program must have a main() function of type int.
- undefined reference to some_function()
- The linker could not find the function's definition. If this wasn't a typo, find out what library contains the function and use -l to link it.
- warning: control reaches end of non-void function
- Your function is missing a return statement.
Managing larger projects with Make
It's a good idea to keep code for different projects in different directories, especially when projects span several files.
For programs involving multiple source files (.c and .cpp), programmers will sometimes compile their files to object code, with the -c option, to fix syntax errors in one file at a time. GCC can then link the *.o object files together to create a program.
username$ gcc -c file1.c
(compiler errors)
username$ nano file1.c # edit the file to correct the errors
username$ gcc -c file1.c
username$ gcc -c file2.c
username$ gcc file1.o file2.o -o myProgram
(linker errors)
username$ gcc file1.o file2.o -o myProgram -lsomelib
username$ ./myProgram
You can automate this process with the program make and a list of instructions called a Makefile.
The Makefile is a plain text file named Makefile (no extension) in the same directory as your source code. The Makefile is its own language. There are two different versions of Make, BSD Make (aka bmake) and GNU Make aka gmake). The Student server uses GNU Make.
# Example Makefile # This is a comment CC = gcc # Create a variable called CC CFLAGS = -g # Create a variable called CFLAGS # Specify target "all" which requires files c_test and d_test all: c_test d_test # The first target in a Makefile is the default target # so when we run make with this file, it will try to build "all" # Specify target c_test which require files c_test.o, container.o, error.o # and specify a command to run c_test: c_test.o container.o error.o $(CC) $(CFLAGS) c_test.o container.o error.o -o c_test -lm # ^^^^^^ This indentation must be a tab, not spaces # Also, note how variables are referenced in a Makefile # Now we'll specify targets for d_test and the object files d_test: d_test.o container.o error.o $(CC) $(CFLAGS) c_test.o container.o error.o -o d_test -lm c_test.o: c_test.c $(CC) $(CFLAGS) -c c_test.c d_test.o: d_test.c $(CC) $(CFLAGS) -c d_test.c container.o: container.c $(CC) $(CFLAGS) -c container.c error.o: error.c $(CC) $(CFLAGS) -c error.c # A target to clean up the directory clean: rm *.o c_test d_test core
Run make in the same directory as your source code and its Makefile, and make will try to build the first target in the Makefile. If it does not have the prerequisites, it will recursively try to build those, and their prerequisites, and so on. If you want to build a specific target, type make targetname, like make all or make d_test.o or make clean. Make compares file timestamps, so if you change one file, you can just run make and rebuild the entire program.
If you only want to see what commands make will run but do not want it to actually run them, make -n will perform a dry run.
Important note: The indentation before a command in a Makefile must be a tab character, not spaces, or else make will not run!
Makefile:13: *** missing separator. Stop.
Make can also build programs without a makefile if your program is in a single source code file. If your file is foo.cpp, type make foo and make will run g++ foo.cpp -o foo.
For more information about Make, read the GNU Make manual.
Debugging C and C++ Programs
The easiest way to track down a bug is the "divide and conquer" method of putting extra printf or cout statements in your code until you find out where it is crashing and why. If you suspect your program might be mishandling memory, link with -l efence to use the Electric Fence malloc() wrapper, which will crash your program the moment it tries to access out-of-bounds memory. For more difficult errors, you might need a debugger.
A debugger lets you meander through a running program to analyse and change the program's data at any point. The debugger on Student is gdb, the GNU Debugger. To run GDB on your program, type gdb a.out where a.out is your program's name. You will begin at GDB's command prompt.
Before you run the program, you should set up breakpoints where you want the program to pause so you can analyse it. Otherwise, the program will run to completion before you can issue any debugger commands. Use break functionName or break file.cpp:123 to set up a breakpoint at the start of a function or a specific line number. break main will pause the program as soon as it starts.
Now you can say run and the debugger will run your program until it reaches a breakpoint, then pause it and wait for further commands. Later, you can use the clear command to clear a breakpoint the same way you set it. More commands are listed in GDB's help, but here are some you may use often:
bt full Show the contents of the stack.
show varName Show the value of a variable.
set varName = value Set a variable.
step Go to the next line of code,
or debug the function called on this line.
next Go to the next line of code,
running by functions without debugging them
finish Run the program until the end of this function
continue Run the program until the next breakpoint.
Sometimes when your program crashes, your shell produces a core file. This is the chunk of memory containing your program and its data at the time it crashed. From the shell, run gdb a.out core (where "a.out" is the name of your program) to see where your program crashed.
Without debugging symbols, a backtrace gives you limited information:
(gdb) bt full #0 0x080484fc in bar () No symbol table info available. #1 0x0804850f in foo () No symbol table info available. #2 0x08048527 in main () No symbol table info available.With debugging symbols (gcc -g), it tells you exactly where the program crashed:
#0 0x080484fc in bar () at buggy.cpp:10 10 fnord = *myPtr;and the backtrace gives you more information:
(gdb) bt full #0 0x080484fc in bar () at buggy.cpp:10 fnord = -1073763100 myPtr = (int *) 0x0 #1 0x0804850f in foo () at buggy.cpp:14 No locals. #2 0x08048527 in main () at buggy.cpp:20 No locals.
For more information about GDB, read the GDB manual.
Go to the Student server home page
Questions or comments about Student may be directed to your instructor, the computer lab staff or:
