페이지

2020년 6월 19일 금요일

Capturing Compiler Output

* There are other ways to capture the output of a compiler (or of any running program). You can run the compiler within the emacs editor, which then gives you a simple command to move from error message to error message, automatically bringing up the source code file on the line cited in the error message. This works with both the UNIX and the MS Windows ports of emacs, and is the technique I use myself. Vim, the "vi improved" editor will do the same.

* Finally, in UNIX there is a command clalled "script" that causes all output to your screen to be captured in a file.
Just say

script log.txt

and all output to your screen will be copied into log.txt until you say

exit

* script output can be kind of ugly, because it includes all the control characters that you type or that your programs use to control formatting on the screen, but it's still useful.


Pipes and redirection

* We introduced pipes and redirection earlier. The complicating factor here is that what you want to pipe or redirect is not the standard output stream, but the standard error stream. So, for example, doing something like

g++ myprogram.cpp >  compilation.log

or 

g++ mayprogram.cpp | more


* It won't work, because these commands are only redirecting the standard output stream. The error message will continue to blow on by.

How you pipe or redirect the standard error stream depends on the shell you are running:

Unix, running C-shell or TC-shell

* The > and | symbols can be modified to affect the standard error stream by appending a '&' character. So these commands do work:

g++ myprogram.cpp >& compilation.log
g++ myprogram.cpp |& more


* A useful program in this regard is tee, which copies its standard input both into the standard output and into a named file:

g++ myprogram.cpp |& tee compilation.log

Linux/CygWin , running bash

* The sequence "2>&1" in a command means "force the standard error to go wherever the standard output is going".. So we can do any of the following:

g++ myprogram.cpp 2>&1 > compilation.log
g++ myprogram.cpp 2>&1 | more

and we can still use tee:

g++ myporogram.cpp 2>&1 | tee compilation.log









Error Messages

* Unfortunately, once you start writing your own code, you will almost certainly make some mistakes and get some error messages from the compiler.

This is likely to lead to two problems: reading the messages, and understanding the messages.
Capturing the Error Messages



*Unless you are a far better programmer than I, you will not only get error messages, you will get so many that they overflow your telnet/xterm window.

How you handle this problem depends upon what command shell you are running, but there are two general approaches. You can use redirection and pipes to send the error messages somewhere more convenient or you can use programs that try to capture all output from a running program (i.e., the compiler).

*We've talked before about how many Unix commands are "filters", working from a single input stream and producing a single output stream. Actually, there are 3 standard streams in most operating systems: standard input, standard output, and standard error. These generally default to the keyboard for standard input and the screen for the other two, unless either the program or the person running the program redirects one or more of thes streams to a file or pipes the stream to / from another program.




Linkage Flags

-L directory                 Add directory to the list of places searched for precompiled libraries.
-llibname                   Link with the precompiled library liblibname.a


Compilation Flags

-c                               compile only, do not link
-o filename                  Use filename as the name of the compiled program
-Dsymbol-value             Define symbol during compilation.
-g                              Include debugging information in compiled code
                                 (required if you want to be able to run the gdb degugger).
-O                              Optimize the compiled code (produces smaller, faster programs
                                 but takes longer to compile)
-l directory                  Add directory to the list of places searched when a "system"
                                include (#include ...) is encountered.


Compiling With Multiple Non-Header Files

* A typical program will consist of many .cpp files. (See Figure 7.1, "Building 1 program from many files") Usually, each class or group of utility functions will have their definitions in a separate .cpp file that defines everything declared in the corresponding .h file. The .h file can then be #included by many different parts of the program that use those classes or functions, and the  .cpp file can be separately compiled once, then the resulting object code file is linked together with the object code from other .cpp files to form the complete program.

* Splitting the program into pieces like this helps, among other things, divide the responsibility for who can change what and reduces the amount of compilation that must take place after a change to a function body.

* When you have a program consisting of multiple files to be compiled separately, add a -c option to each compilation. This will cause the compiler to generate a .o object code file instead of an executable. Then invoke the compiler on all the .o files together without the -c to link them together and produce an executable:

g++ -g -c file1.cpp
g++ -g -c file2.cpp
g++ -g -c file3.cpp
g++ -g -o programName file1.o file2.o file3.o

* (If there are no other .o files in that directory, the last command can often be abbreviated to "g++ -o programName -g *.o".) The same procedure works for the gcc compiler as well.

Actually, you don't have to type separate compilation commands for each file. You can do the whole thing in one step:

g++ -g -o programName file1.cpp file2.cpp file3.cpp

* But the step-by-step procedure is a good habit to get into. As you begin debugging your code, you are likely to make changes to only one file at a time. If, for example, you find and fix a bug in file2.cpp, you need to only recompile that file and relink:

g++ -g -c file2.cpp
g++ -g -o programName file1.o file2.o file3.o

Use an editor (e.g., emacs) to prepare the follwing files:

hellomain.cpp

#include <iostream>
#include "sayhello.h"

using namespace std;

int main()
{
   sayHello();
   return 0;
}


sayhello.h

#ifndef SAYHELLO_H
#define SAYHELLO_H

void sayHello();

#endif

sayhello.cpp

#include <iostream>;
#include "sayhello.h"

using namespace std;

void sayHello()
  count << "hello in 2parts!" << endl;
}

* To compile and run these, give the commands:

g++ -g -c sayhello.cpp
g++ -g -c hellomain.cpp
ls
g++ -g -o hello1 sayhello.o hellomain.o
ls
./hello1


* Note, when you do the first ls, tht the first two g++ invocations created some .o files.

Alternatively, you can compile these in one step. Give the command

rm hello1 *.o
ls

just to clean up after the previous steps, then try compiling this way:

g++ -g -o hello2 hellomain.cpp sayhello.cpp
ls 
./hello2

* An even better way to manage multiple source files is to use the make command.
Some Useful Compiler Options

* Another useful option in these compilers is -D. If you add an option -Dname=value, then all occurrences of the identifier name in the program will be replaced by value. This can be useful as a way of customizing programs without editing them. If you use this option without a value, -Dname, then the compiler still notes that name has been "defined". This is useful in conjunction with compiler directive ifdef, which causes certain code to be compiled only if a particular name is defined. For example, many programmers will insert debugging output into their code this way:

...
x= f(x,y,z);
#ifdef DEBUG
   cerr << "the value of X is: " << x <<endl;
#endif
y=g(z,x);
...

* The output statement in this code will be ignored by the compiler unless the option -DDEBUG is included in the command line when the compiler is run.[38]

Sometimes your program may need functions from a previously-compiled library. For example, the sqrt and other mathematical functions are kept in the "m" library (the filename is actually libm.a). To add functions from this library to your program, you would use the "-lm" option. (The "m" in "-lm" is the library name.) this is a linkage option, so it goes at the end of the command:

g++ -g -c file1.cpp
g++ -g -c file2.cpp
g++ -g -c file3.cpp
g++ -g -o programName file1.o file2.o file3.o -lm

The general form of gcc/g++ commands is g++ compilation-option files linker-options Here is a summary of the most commonly used options for gcc/g++:

 





























Compiling a Program with Only One Non-Header File

Use an editor (e.g., emacs) to prepare the following files:

hello.cpp

#include <iostream>

using namespace std;

int main()
{
   count << "Hello from C++!" << endl;
   return 0;
}

hello.c

#include<stdio.h>
int main()
{
   printf("Hello from C!\n");
   return 0;
}

To compile and run these, give the commands:

g++ -g hello.cpp
ls

Notice that a file a.out has been created.

./a.out
gcc -g hello.c
./a.out

* The compiler generates an executable program called a.out If you don't like that name, you can use the mv command to rename it.

Alternatively, use a -o option to specify the name you would like for the compiled program:

g++ -g -o hello1 hello.cpp
./hello1
gcc -g -o hello2 hello.c
./hello2

* In the example above, we placed "./" in front of the file name of our compiled program to run it, in general, running prgrams is no different from running ordinary Unix commands. You just type

pathToProgramOrCommand parameters

In fact, almost all of the "commands" that we have used in this course are actually programs that were compiled as part of the installation of the Unix operation system.

* As we have noted earlier, we don't usually give the command/program name as a lengthy file path. We say, for example, "ls" instead of "/bin/ls". That works because certain directories, such as /bin, are automatically searched for a program of the appropriate name. This set of directories is referred to as your execution path. Your account was set up so that the directories holding the most commonly used Unix commands and programs are already in the execution path. You can see your path by giving the command

* echo $PATH

One thing that you will likely find i that your $PATH probably does not include ".", your current directory. Placing the current directory into the $PATH is considered a (minor) security risk, but that means that, if we had simply typed "a.out" or "hello", those programs would not have been found because the current directory is not in the search path. Hence, we gave the explicit path to the program files, "/a.out" and "./hello".