Функциональные CFG связаны между собой местами для звонков. Если одна функция вызывает другую, например ::1001
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
до оператора после вызова foo
в baz
. Здесь следующим оператором является вызов bar
в строке 5. В этот момент существует край от пункта вызова bar
до CFG bar
и линии от точек выхода в bar
до конца. baz
.
По сути, все, что вам нужно подумать, это «какой код будет выполняться дальше». Это говорит о том, куда должны идти ребра в вашем контрольном графе. Вызов функции передает управление до тех пор, пока функция не вернется, что подразумевает переход от места вызова к функции CFG и обратно.
Обратите внимание, что не все полнопрограммные CFG являются связными графами. Если в анализируемой программе есть недостижимый код , то это будет его собственный неподключенный фрагмент полного CFG. например если вы ответите на вызов bar
в приведенном выше примере, тогда bar
будет иметь свой собственный график в стороне, а baz
и foo
будут соединены ребрами.