We have previously seen that compilers often represent code as trees.
For example, in this case, the expression is
Each non-leaf node represents the application of some operator to one or more subexpressions.
As good C++ programmers, we know that assignment and many other “built-in” parts of C++ are just more operators, so we can easily extend this idea to entire statements…
This idea can be extended to other kinds of statements as well, if we're willing to be a bit loose in our interpretation of what an “operator” is.
Here, for example, is a structure that might be used to represent a declaration of a variable
int x = 0;
Now, let's consider what happens just a little bit later in the compilation. We have trees for expressions and statements, and trees for declarations. All of these trees are actually joined together as subtrees of larger trees representing entire functions, classes, and other larger C++ constructs.
So, we have a tree for, say, the assignment
x = (13 + a) * (x - 1);
Now the compiler wants to know just what “x”
actually refers to. In a typical C++ program, we may have lots of objects
named “x”, occurring in different functions, as data members of
different classes and structs, etc. Each of these “x” objects
has a unique tree representing its declaration, but which of those
declarations are this assignment statement's
Well, the language has various rules allowing the compiler to resolve this question, and typically once the compiler has figured out the answer, it records that answer by adding a pointer from the uses of “x” to the appropriate declaration, as shown here.
We add pointers
from each use of a name
to the declaration to which it refers.
So here we have a perfectly useful data structure. But what is it?
Whatever this is, it's not a tree anymore!
This structure is an example of a graph.
The compiler must traverse this structure, generating code for each node of the tree. Processing graphs requires different kinds of algorithms from what we used for trees. Obviously we don't want to generate code multiple times for the same nodes, but this example shows that in a graph, we can reach the same nodes multiple times using different paths. Even worse, recursion and other constructs can lead to cycles (loops) in the graph, but we still need to make sure that traversals will terminate.
As another example, consider the map representing the flights offered by a small airline.
This is also a graph. It can be used in such practical problems as
Given travel time on each route, find the fastest way to travel between two cities.
Find the fastest way to visit every city in the graph.