5.13.5.3.2.2.1. 按调用次序排序
为了下面处理的方便起见,在 1253 行,把所有的 cgraph_node 按对应函数调用的次序排序,把有向图转化为队列(其满足拓扑排序( topological order )的定义)。
548 static int
549 cgraph_postorder (struct cgraph_node **order) in cgraphunit.c
550 {
551 struct cgraph_node *node, *node2;
552 int stack_size = 0;
553 int order_pos = 0;
554 struct cgraph_edge *edge, last;
555
556 struct cgraph_node **stack =
557 xcalloc (cgraph_n_nodes , sizeof (struct cgraph_node *));
558
559 /* We have to deal with cycles nicely, so use a depth first traversal
560 output algorithm. Ignore the fact that some functions won't need
561 to be output and put them into order as well, so we get dependencies
562 right through intline functions. */
563 for (node = cgraph_nodes ; node; node = node->next)
564 node->aux = NULL;
565 for (node = cgraph_nodes ; node; node = node->next)
566 if (!node->aux)
567 {
568 node2 = node;
569 if (!node->callers)
570 node->aux = &last;
571 else
572 node->aux = node->callers;
573 while (node2)
574 {
575 while (node2->aux != &last)
576 {
577 edge = node2->aux;
578 if (edge->next_caller)
579 node2->aux = edge->next_caller;
580 else
581 node2->aux = &last;
582 if (!edge->caller->aux)
583 {
584 if (!edge->caller->callers)
585 edge->caller->aux = &last;
586 else
587 edge->caller->aux = edge->caller->callers;
588 stack[stack_size++] = node2;
589 node2 = edge->caller;
590 break ;
591 }
592 }
593 if (node2->aux == &last)
594 {
595 order[order_pos++] = node2;
596 if (stack_size)
597 node2 = stack[--stack_size];
598 else
599 node2 = NULL;
600 }
601 }
602 }
603 free (stack);
604 return order_pos;
605 }
这里使用的算法是 Kahn 在 1962 年提出的,具体的算法如下:
L ← Empty list that will contain the sorted elements
S ← Set of all nodes with no incoming edges
while S is non-empty do
remove a node n from S
insert n into L
for each
node m with an edge e
from n to m do
remove edge e from the graph
if
m has no other incoming edges then
insert m into S
if graph has edges then
output error message (graph has at least one cycle)
else
output message (proposed topologically sorted order: L)
不过在这里的实现中,环( cycle )不是问题(由直接或间接递归产生),因为 C++ 程序总是从 main 函数开始的,出现环时,以离 main 函数最远的函数作为起点。毫无疑问,递归的函数是不能内联的,否则会导致无限展开。不过这里我们暂时不管它。
以下面的函数调用关系为例,我们已经知道 cgraph_node 节点是在函数第一次被调用时构建的,那么它们在 cgraph_nodes 中的次序就反映了调用时间的先后。在下图中,函数的调用时间次序是: 0 -> 1-> 2-> 3-> 1 。
图 119 :对节点 0 的处理
因为函数 0 没有调用者,很清晰地,它就是排序后的第一个节点。
图 120 :对节点 1 的处理
函数 1 出现在一个递归环里,那么其节点的 AUX 追踪到下一个调用者的 cgraph_edge 。
图 121 :对节点 1 的第二次处理
重复处理函数 1 ,把它缓存入 stack ,进入其下一个调用者。
图 122 :对节点 3 的处理
继续深入函数 1 的调用链,函数 3 尚不是最终调用者,亦把它缓存入 stack 。
图 123 :对节点 2 的处理
函数 2 在对函数 1 调用链的最开头,而且它亦为函数 1 所调用,不过函数 1 已被处理,接受它成为排序后节点。
图 124 :对 stack 中节点的处理
Stack 中的节点接着以后进先出的次序取出并进入排序队列。排序后的次序是: 0-> 2 -> 3 -> 1 。