En ciencias de la computación, un gráfico de flujo de control ( CFG) es una representación, usando notación gráfica, de todos los caminos que se pueden atravesar a través de un programa durante su ejecución. El gráfico de flujo de control fue descubierto por Frances E. Allen, quien notó que Reese T. Prosser usó matrices de conectividad booleana para el análisis de flujo antes.
El CFG es esencial para muchas optimizaciones de compiladores y herramientas de análisis estático.
En un gráfico de flujo de control, cada nodo del gráfico representa un bloque básico, es decir, un fragmento de código en línea recta sin saltos ni objetivos de salto ; los objetivos de salto comienzan un bloque y los saltos terminan un bloque. Los bordes dirigidos se utilizan para representar saltos en el flujo de control. En la mayoría de las presentaciones, hay dos bloques especialmente designados: el bloque de entrada, a través del cual el control ingresa al diagrama de flujo, y el bloque de salida, a través del cual sale todo el flujo de control.
Debido a su procedimiento de construcción, en un CFG, cada borde A → B tiene la propiedad de que:
Por lo tanto, el CFG se puede obtener, al menos conceptualmente, comenzando desde el gráfico de flujo (completo) del programa, es decir, el gráfico en el que cada nodo representa una instrucción individual, y realizando una contracción de borde para cada borde que falsifique el predicado anterior, es decir, contraer cada borde cuya fuente tiene una única salida y cuyo destino tiene una única entrada. Este algoritmo basado en contracciones no tiene importancia práctica, excepto como una ayuda de visualización para comprender la construcción del CFG, porque el CFG se puede construir de manera más eficiente directamente desde el programa escaneándolo en busca de bloques básicos.
Considere el siguiente fragmento de código:
0: (A) t0 = read_num 1: (A) if t0 mod 2 == 0 2: (B) print t0 + " is even." 3: (B) goto 5 4: (C) print t0 + " is odd." 5: (D) end program
En lo anterior, tenemos 4 bloques básicos: A de 0 a 1, B de 2 a 3, C a 4 y D a 5. En particular, en este caso, A es el "bloque de entrada", D el "bloque de salida" "y las líneas 4 y 5 son objetivos de salto. Un gráfico para este fragmento tiene bordes de A a B, A a C, B a D y C a D.
La accesibilidad es una propiedad de gráfico útil en la optimización.
Si un subgrafo no está conectado desde el subgrafo que contiene el bloque de entrada, ese subgrafo es inalcanzable durante cualquier ejecución, y también es un código inalcanzable ; en condiciones normales, se puede quitar de forma segura.
Si el bloque de salida es inalcanzable desde el bloque de entrada, puede existir un bucle infinito. No todos los bucles infinitos son detectables, consulte Problema de detención. También puede existir allí una orden de suspensión.
El código inalcanzable y los bucles infinitos son posibles incluso si el programador no los codifica explícitamente: optimizaciones como la propagación constante y el plegado constante seguido de subprocesos de salto pueden colapsar varios bloques básicos en uno, hacer que los bordes se eliminen de un CFG, etc., por lo que posiblemente desconectando partes del gráfico.
Un bloque M domina un bloque N si cada camino desde la entrada que llega al bloque N tiene que pasar por el bloque M. El bloque de entrada domina todos los bloques.
En la dirección inversa, el bloque M posdomina el bloque N si todos los caminos desde N hasta la salida tienen que pasar por el bloque M. El bloque de salida posdomina todos los bloques.
Se dice que un bloque M domina inmediatamente el bloque N si M domina N, y no hay un bloque P intermedio tal que M domine P y P domine N. En otras palabras, M es el último dominador en todos los caminos desde la entrada a N. Cada bloque tiene un dominador inmediato único.
De manera similar, existe una noción de postdominador inmediato, análoga a dominador inmediato.
El árbol de dominador es una estructura de datos auxiliares que representa las relaciones de dominador. Hay un arco desde el Bloque M al Bloque N si M es un dominador inmediato de N. Este gráfico es un árbol, ya que cada bloque tiene un dominador inmediato único. Este árbol tiene sus raíces en el bloque de entrada. El árbol dominador se puede calcular de manera eficiente utilizando el algoritmo de Lengauer-Tarjan.
Un árbol posdominador es análogo al árbol dominador. Este árbol tiene sus raíces en el bloque de salida.
Un borde posterior es un borde que apunta a un bloque que ya se ha cumplido durante un recorrido del gráfico en profundidad primero ( DFS ). Los bordes posteriores son típicos de los bucles.
Un borde crítico es un borde que no es el único borde que sale de su bloque de origen ni el único borde que entra en su bloque de destino. Estos bordes deben dividirse: se debe crear un nuevo bloque en el medio del borde, para poder insertar cálculos en el borde sin afectar ningún otro borde.
Un borde anormal es un borde cuyo destino se desconoce. Las construcciones de manejo de excepciones pueden producirlas. Estos bordes tienden a inhibir la optimización.
Un borde imposible (también conocido como borde falso) es un borde que se ha agregado al gráfico únicamente para preservar la propiedad de que el bloque de salida domina todos los bloques. Nunca se puede atravesar.
Un encabezado de bucle (a veces llamado el punto de entrada del bucle) es un dominador que es el objetivo de un borde posterior que forma un bucle. El encabezado del bucle domina todos los bloques del cuerpo del bucle. Un bloque puede ser un encabezado de bucle para más de un bucle. Un bucle puede tener varios puntos de entrada, en cuyo caso no tiene un "encabezado de bucle".
Suponga que el bloque M es un dominador con varios bordes entrantes, algunos de ellos son bordes traseros (por lo que M es un encabezado de bucle). Es ventajoso para varias pasadas de optimización dividir M en dos bloques M pre y M bucle. El contenido de M y los bordes posteriores se mueven al bucle M, el resto de los bordes se mueven para apuntar a M pre y se inserta un nuevo borde de M pre al bucle M (de modo que M pre es el dominador inmediato del bucle M). Al principio, M pre estaría vacío, pero pasa como el movimiento de código invariante de bucle podría poblarlo. M pre se denomina preencabezado del bucle y M bucle sería el encabezado del bucle.
Un CFG reducible es uno con bordes que se pueden dividir en dos conjuntos disjuntos: bordes delanteros y bordes posteriores, de manera que:
Los lenguajes de programación estructurados a menudo se diseñan de manera que todos los CFG que producen son reducibles, y las declaraciones de programación estructuradas comunes como IF, FOR, WHILE, BREAK y CONTINUE producen gráficos reducibles. Para producir gráficos irreductibles, se necesitan declaraciones como GOTO. Algunas optimizaciones del compilador también pueden producir gráficos irreducibles.
La conectividad de bucle de un CFG se define con respecto a un árbol de búsqueda en profundidad (DFST) dado del CFG. Este DFST debe estar enraizado en el nodo de inicio y cubrir todos los nodos del CFG.
Los bordes en el CFG que van desde un nodo hasta uno de sus antepasados DFST (incluido él mismo) se denominan bordes posteriores.
La conectividad del bucle es el mayor número de bordes posteriores que se encuentran en cualquier ruta libre de ciclos del CFG. En un CFG reducible, la conexión del bucle es independiente del DFST elegido.
La conectividad de bucle se ha utilizado para razonar sobre la complejidad temporal del análisis de flujo de datos.
Mientras que los gráficos de flujo de control representan el flujo de control de un solo procedimiento, los gráficos de flujo de control entre procedimientos representan el flujo de control de programas completos.
![]() | Wikimedia Commons tiene medios relacionados con Gráfico de flujo de control. |