我现在正在学习编译器课程,我们到了构建CFG的阶段,以实现优化。有一件事情我无法理解,就是一个程序有多少个CFG?我见过的每个例子似乎都只是一个简单代码段的CGF。因此,如果你有一个包含三个函数的程序,那么你是为每个函数单独创建一个CFG,还是创建一个大的CFG来覆盖整个程序呢?
我现在正在学习编译器课程,我们到了构建CFG的阶段,以实现优化。有一件事情我无法理解,就是一个程序有多少个CFG?我见过的每个例子似乎都只是一个简单代码段的CGF。因此,如果你有一个包含三个函数的程序,那么你是为每个函数单独创建一个CFG,还是创建一个大的CFG来覆盖整个程序呢?
函数级别的控制流图通过调用点相互连接。如果一个函数调用了另一个函数,例如:
0 void foo() { /* do stuff */ }
1 void bar() { /* do stuff */ }
2
3 void baz() {
4 foo(); // Callsite for foo. Control transfers to foo, then foo returns here.
5 bar(); // Callsite for bar. Control transfers to bar, then bar returns here.
6 }
baz
的控制图包括一个指向 foo
图的边,则由于 foo
最终会返回到 baz
(以及它可能被调用的任何其他地方),因此从 foo
图的末尾返回到 baz
中对 foo
调用后的语句会有一条边。在这里,下一条语句是第 5 行对 bar
的调用。此时,从 bar
调用点到 bar
的 CFG 有一条边,并且从 bar
的退出点到 baz
的结尾也有边。
基本上,你只需要考虑“下一个执行的代码是什么”。这告诉你在控制图中应该放置哪些边缘。函数调用将控制权传递到函数返回,这意味着从调用点到函数 CFG 和再次返回之间有一条边。
请注意,并非所有完整程序的CFG都是连通图。如果在分析的程序中存在无法到达的代码,那么它将成为完整CFG的独立未连接部分。例如,如果您在上面的示例中删除对bar
的调用,则bar
将拥有自己的图形,而baz
和foo
将通过边缘相连。你可以为每个函数构建一个CFG,然后根据需要将它们组合成完整的CFG。整个程序的CFG可能非常大,因此通常不适合作为示例。