Dentro de la informática , una cadena de uso-definición (o cadena UD ) es una estructura de datos que consta de un uso U , de una variable y de todas las definiciones D de esa variable que pueden llegar a ese uso sin ninguna otra definición intermedia. [1] [2] Una cadena UD generalmente significa la asignación de algún valor a una variable.
Una contraparte de una cadena UD es una cadena de definición-uso (o cadena DU ), que consiste en una definición D de una variable y todos los usos U alcanzables desde esa definición sin ninguna otra definición intermedia. [3]
Tanto las cadenas UD como las DU se crean mediante una forma de análisis de código estático conocido como análisis de flujo de datos . Conocer las cadenas use-def y def-use de un programa o subprograma es un requisito previo para muchas optimizaciones del compilador , incluidas la propagación constante y la eliminación de subexpresiones comunes .
Realizar las cadenas de uso-definición o definición-uso es un paso en el análisis de vitalidad , de modo que se puedan identificar y rastrear representaciones lógicas de todas las variables a través del código.
Considere el siguiente fragmento de código:
int x = 0 ; /* A */ x = x + y ; /* B */ /* 1, algunos usos de x */ x = 35 ; /* C */ /* 2, algunos usos más de x */
Observe que x
se le asigna un valor en tres puntos (marcados A, B y C). Sin embargo, en el punto marcado "1", la cadena de definición de uso para x
debe indicar que su valor actual debe haber venido de la línea B (y su valor en la línea B debe haber venido de la línea A). Por el contrario, en el punto marcado "2", la cadena de definición de uso para x
indica que su valor actual debe haber venido de la línea C. Dado que el valor de x
en el bloque 2 no depende de ninguna definición en el bloque 1 o anterior, x
bien podría ser una variable diferente allí; prácticamente hablando, es una variable diferente - llámela x2
.
int x = 0 ; /* A */ x = x + y ; /* B */ /* 1, algunos usos de x */ int x2 = 35 ; /* C */ /* 2, algunos usos de x2 */
El proceso de división x
en dos variables independientes se denomina división de rango en vivo. Véase también formulario de asignación única estática .
La lista de declaraciones determina un orden fuerte entre las declaraciones.
Para una variable, como v , su declaración se identifica como V (letra mayúscula cursiva) y, para abreviar, su declaración se identifica como . En general, una declaración de una variable puede estar en un ámbito externo (por ejemplo, una variable global ).
Cuando una variable, v , está en el lado izquierdo de una declaración de asignación, como , entonces es una definición de v . Cada variable ( v ) tiene al menos una definición por su declaración ( V ) (o inicialización).
Si la variable, v , está en el lado derecho del enunciado , hay un enunciado, con i < j y , que es una definición de v y tiene un uso en (o, en resumen, cuando una variable, v , está en el lado derecho de un enunciado , entonces v tiene un uso en el enunciado ).
Consideremos la ejecución secuencial de la lista de declaraciones, , y lo que ahora se puede observar como el cálculo en la declaración, j :
Este ejemplo se basa en un algoritmo Java para encontrar el mcd . (No es importante entender lo que hace esta función).
/*** @param(a, b) Los valores utilizados para calcular el divisor.* @return El máximo común divisor de a y b.*/int mcd ( int a , int b ) { int c = a ; int d = b ; si ( c == 0 ) devuelve d ; mientras ( d != 0 ) { si ( c > d ) c = c - d ; demás d = d - c ; } devuelve c ; }
Para conocer todas las cadenas de uso definido para la variable d, realice los siguientes pasos:
d=b
" (l.7)return d
"[d, d=b, return d]
Repita estos pasos en el siguiente estilo: combine cada acceso de escritura con cada acceso de lectura (pero NO al revés).
El resultado debería ser:
[ d , d = b , devuelve d ] [ d , d = b , mientras que ( d != 0 )] [ d , d = b , si ( c > d )] [ d , d = b , c = c - d ] [ d , d = b , d = d - c ] [ d , d = d - c , mientras que ( d != 0 )] [ d , d = d - c , si ( c > d )] [ d , d = d - c , c = c - d ] [ d , d = d - c , d = d - c ]
Hay que tener cuidado si la variable cambia con el tiempo.
Por ejemplo: desde la línea 7 hasta la línea 13 del código fuente, d no se redefine ni se modifica. En la línea 14, d podría redefinirse. Por eso, hay que volver a combinar este acceso de escritura en d con todos los posibles accesos de lectura a los que se pueda acceder. En este caso, solo es relevante el código que se encuentra más allá de la línea 10. Por ejemplo, no se puede acceder de nuevo a la línea 7. Para que lo entiendas, puedes imaginar dos variables diferentes d :
[ d1 , d1 = b , devuelve d1 ] [ d1 , d1 = b , mientras que ( d1 != 0 )] [ d1 , d1 = b , si ( c > d1 )] [ d1 , d1 = b , c = c - d1 ] [ d1 , d1 = b , d1 = d1 - c ] [ d2 , d2 = d2 - c , mientras que ( d2 != 0 )] [ d2 , d2 = d2 - c , si ( c > d2 )] [ d2 , d2 = d2 - c , c = c - d2 ] [ d2 , d2 = d2 - c , d2 = d2 - c ]
Como resultado, se podría obtener algo como esto. La variable d1 se reemplazaría por b .
/*** @param(a, b) Los valores utilizados para calcular el divisor.* @return El máximo común divisor de a y b.**/int mcd ( int a , int b ) { int c = a ; entero d ; si ( c == 0 ) devolver b ; si ( b != 0 ) { si ( c > b ) { c = c - b ; d = b ; } demás d = b - c ; mientras ( d != 0 ) { si ( c > d ) c = c - d ; demás d = d - c ; } } devuelve c ; }
Con este algoritmo se consiguen dos cosas: