Pregunta:
¿Existe una herramienta de análisis de código que pueda reducir mi ramificación condicional?
Niklas R.
2016-05-01 13:56:38 UTC
view on stackexchange narkive permalink

Quiero reducir la ramificación anidada profunda de este código C y me pregunto si existe una herramienta de análisis que pueda hacer una tabla de verdad para las condiciones o si debo analizarla manualmente. Me gustaría hacer que el código sea más legible y menos ramificado. Mi IDE CLion de JetBrains no dice nada sobre cómo realizar tal refactorización. ¿Se puede automatizar? No intenté usar Lint pero pude probarlo.

  if (ptr + j) {if (* (ptr + j) [0] == '{') {keep = true; } if (testFn (* (ptr + j))) {/ * prueba para el último carácter * / cadena [i] [j - p] = concat (* pString1, * (ptr + j)); mantener = falso; libre (* pString1); goto mylabel; } if (mantener) {* pString1 = concat (* pString1, * (ptr + j)); * pString1 = concat (* pString1, ""); p ++; } más {b1 ​​= falso; int q = j; for (e = 0; * (ptr + q + e); e ++) {/ * paso a través de la cadena * / b1 = true; if (* (ptr + e + q)) {* pString = concat (* pString, * (ptr + e + q)); * pString = concat (* pString, ""); } j = e; } if (makeArgs (* pString, &argc, (const char ***) &argv, pipe, i, h)) {write_command (&w, argv, string [w]); w ++; } else {if (! b1) {/ * no args (?) * / for (int r = 0; argv [r]! = NULL; r ++) {string [i] [r] = argv [r]; /* ¿Es esto necesario? * /}}}}}  

He podido reducir la ramificación condicional manualmente, pero no he hecho una tabla de verdad. Creo que los análisis de código deberían decir qué ramas son idénticas y cuándo con una tabla de verdad.

La función completa se ve así hoy:

  static int runCmd (const char * cmd) {const char * cp; pid_t pid; estado int; comando de estructura comando de estructura [15]; char ** argv = 0; int argc = 1; bool pipe = falso; char * cadena [z] [z];
char * pString3 [40]; char * pString2 [40]; int n = 0; char ** ptr1; char string1 [z]; bool mantener = falso; char * pString1 [z]; char * pString [z]; * pString1 = "\ 0"; * pString = "\ 0"; char * temp = {'\ 0'}; int w = 0; bool quote = false; int rrs [256]; int j = 0; int i; int p = 0; char ** ptr; int count = 0; char * cmdtmp; bool b1 = falso; int y = 0; i = 0; int h = 0; char * str; char * freeme [75] [75]; char ** dealloc [75]; char ** dealloca [75] [75]; int acount [128]; nullterminate (cadena); int rr = 0; para (z = 0; z < 128; z ++) {cuenta [z] = -1; } para (int f = 0; f < 75; f ++) {dealloc [f] = NULL; para (z = 0; z < 75; z ++) {freeme [f] [z] = NULL; }} if (cmd) {for (cp = cmd; * cp; cp ++) {if ((* cp > = 'a') && (* cp < = 'z')) {continuar; } if ((* cp > = 'A') && (* cp < = 'Z')) {continuar; } if (isDecimal (* cp)) {continuar; } if (isBlank (* cp)) {continuar; } si ((* cp == '.') || (* cp == '/') || (* cp == '-') || (* cp == '+') || (* cp == '=') || (* cp == '_') || (* cp == ':') || (* cp == ',') || (* cp == '\' ' ) || (* cp == '"')) {continuar;}} cmdtmp = strdup (cmd); ptr1 = str_split (pString3, cmdtmp, '|'); if (strstr (cmd," | ") == NULL) {/ * no es una canalización * / makeArgs (cmd, &argc, (const char ***) &argv, pipe, 0, 0); write_argument (&argc, structcommand, argv, string [0]); n ++;} else {for (i = 0; * (ptr1 + i); i ++) {/ * bucle para cada canalización * / n ++;
/ * guardar número de canalizaciones * / dealloc [n] = NULL; int e = 0; / * un contador * / * pString = "\ 0"; / * ¿Debería malloc y liberar esto? * / strcpy (cadena1, * (ptr1 + i)); if ((string1 [0]! = '\ 0') &&! isspace (string1 [0])) {/ * este no es ni el final ni un nuevo argumento * / / * ¿Error BSD? comprobar * / ptr = str_split (pString2, * (&string1), ''); / * divide la cadena en los argumentos * / dealloc [rr] = ptr; rr ++; h = 0; for (j = 0; * (ptr + j); j ++) {/ * recorrer los argumentos * / dealloca [n] [n - 1] = NULL; / * la canalización está en cmdtmp y el argumento / programa está en ptr [i] * / if (ptr + j &&! quote && strstr (* (ptr + j), "'")) {/ * es una cita? * / quote = true; strcpy (temp, * (ptr + j)); / * punto donde chocan los piipelines citados * / if (y < 1) {y ++; }} while (quote) {if (* (ptr + j) && strstr (* (ptr + j), "'")) {/ * fin de la cita * / quote = false; if (y < 1) {cadena [i] [j] = strcpy (temp, * (ptr + j)); } y = 0; } else if (* (ptr + j)) {/ * leer hasta el final de la cita * / string [i] [j] = temp; Seguir; } más {cita = falso; romper;
}} si (ptr + j) {; if (* (ptr + j) [0] == '{') {mantener = verdadero; } if (testFn (* (ptr + j))) {/ * prueba para el último carácter * / string [i] [j - p] = concat (* pString1, * (ptr + j)); mantener = falso; libre (* pString1); continuar; // ir a mylabel; } if (mantener) {str = concat (* pString1, * (ptr + j)); * pString1 = concat (str, ""); libre (str); p ++; } más {b1 ​​= falso; int q = j; freeme [i] [0] = * pString; for (e = 0; * (ptr + q + e); e ++) {/ * paso a través de la cadena * / b1 = true; if (* (ptr + e + q)) {str = concat (* pString, * (ptr + e + q)); * pString = concat (str, ""); / * ¿cómo liberar ()? * / libre (str); freeme [i] [e] = * pString; } j = e; / * ajustar el contador * /} if (makeArgs (freeme [i] [e - 1], &argc, (const char ***) &argv, pipe, i, h)) {write_command (&w, argv, string [w ]); w ++; para (int qwe = 0; qwe < argc; qwe ++) {
dealloca [n - 1] [qwe] = &argv [qwe]; } cuenta [n - 1] = argc; } else {if (! b1) {/ * no args (?) * / for (int r = 0; argv [r]! = NULL; r ++) {string [i] [r] = argv [r]; /* ¿Es esto necesario? * /}}}}}} bool boo = falso; dump_argv ((const char *) "d", argc, argv, boo); }}} para (i = 0; i < n; i ++) {structcommand [i] .argv = string [i]; for (j = 0; string [i] [j]! = NULL; j ++) {if (string [i]! = NULL) {}}} libre (cmdtmp); if (ptr1) {int i; para (i = 0; * (ptr1 + i); i ++) {libre (* (ptr1 + i)); } printf ("\ n"); libre (ptr1); } fflush (NULL); pid = tenedor (); if (pid < 0) {perror ("error de bifurcación"); return -1; } / * Si somos el proceso hijo, entonces ejecutamos la cadena. * / If (pid == 0) {/ * spawn (cmd); * / fork_pipes (n, structcommand); } / * * Somos el proceso padre. * Espere a que el niño complete. * / estado = 0; while (((pid = waitpid (pid, &status, 0)) < 0) && (errno == EINTR)); if (pid < 0) {fprintf (stderr, "Error de waitpid:% s", strerror (errno)); return -1; } if (WIFSIGNALED (estado)) {fprintf (stderr, "pid% ld: asesinado por la señal% d \ n",
(largo) pid, WTERMSIG (estado)); return -1; }} para (i = 0; i < n; i ++) {para (j = 0; cadena [i] [j]! = NULL; j ++) {if (cadena [i]! = NULL) {if (cadena [ i] [j]) libre (cadena [i] [j]); }}} int z; para (int f = 0; f < n; f ++) {if (f > 0) {} para (z = 0; freeme [f] [z]; z ++) {free (freeme [f] [z]); }} size_t idx; for (int f = 0; n > 1 && f < n; f ++) {for (idx = 0; * (dealloc [f] + idx)! = NULL; idx ++) {free (* (dealloc [f] + idx )); } libre (dealloc [f]); } return WEXITSTATUS (estado);}  

El código está escaneando y analizando otro programa, es por eso que hay tanta manipulación de cadenas, guardando y mirando hacia adelante en caracteres y punteros.

Si su código todavía usa `goto`, evite el uso de keep, entonces no lo ha hecho bien.
@SteveBarnes Debería poder reescribir el `goto` pero en realidad me gusta porque es muy raro. Podemos hacerlo con 'break', 'continue` que también me gustan más que las variables. De hecho, si programa en ensamblador, hace `goto` a menudo.
Sí, es esencial en el ensamblador, pero se considera ** muy ** una mala práctica es C debido a los problemas con a) problemas de mantenimiento yb) corrupción de la pila porque es demasiado fácil `ir a algún lugar fuera de la función o procedimiento, no regresar . ¡Por eso es raro!
[Mi proyecto] (https://github.com/montao/openshell) es de aprox. 2000 líneas de código y usé `goto` solo una vez porque era vago. Prometo que lo cambiaré a un "descanso" o un "continuar" pero no me gusta "booleano" ...
Su código parece inspeccionar un valor no inicializado de keep, si sus dos primeros condicionales (anidados) se evalúan como falso. Eso es un error o no nos mostró todo el código relevante.
@IraBaxter Sí, parece así pero está inicializado. Lo que más me preocupa es la corrección, la legibilidad y la capacidad de mantenimiento, así que utilizo la herramienta Valgrind para solucionar todos mis errores y pérdidas de memoria. Incluso si el programa se ejecuta "perfectamente", puede tener pérdidas de memoria que no se muestran hasta que un análisis descubre los errores.
¿Qué quiere decir exactamente con "tabla de verdad"? ¿Quiere saber cuál es la condición completa en la que se ejecuta cada bloque de código? (p. ej., para la parte de "if (mantener)", la condición completa sería "* (ptr + j) [0] == '{' &&! testFn (* (ptr + j)) :? actualice su pregunta para hacer explícito lo que desea.
Este código es muy extraño. Supongo que "ptr" se declara como "char *". ¿Cómo puede "ptr + j" ser cero? ¿Puede explicar qué hace "* (ptr + j) [0]"? ¿Realmente se compila?
@IraBaxter Por lo general, esto se hace con un "árbol de sintaxis abstracta" con un algoritmo de analizador sintáctico de descenso recursivo, pero lo he hecho con bucles y una matriz que representa un programa donde la primera fila es la primera tubería y la primera columna de la primera fila es la primer argumento de la primera tubería. `* (ptr + j) [0]` es en realidad muy concreto el carácter actual de lo que se está escaneando, `ptr` es el comienzo de la tubería y j es el desplazamiento. 0 significa el primer carácter del argumento. Especificación [aquí] (pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html).
Bien, entonces "ptr" se declara "** char". ¿Cómo puede "ptr + j" ser cero? No respondió a la pregunta sobre lo que desea explícitamente para las "tablas de verdad". (¿Están todos esos tamaños de matriz realmente cableados? Este no es código preprocesado; todavía contiene comentarios). (Sí, sé acerca de los analizadores de descenso recursivos, pero generalmente no los implemento de esta manera: consulte http://stackoverflow.com/questions/2245962/is-there-an-alternative-for-flex-bison-that- is-usable-on-8-bit-embedded-systems / 2336769 # 2336769)
Podría hacerlo con recursividad (que se usa para llamar a `fork` y` exec`, pero los bucles son más rápidos y fáciles de codificar que la recursividad. Creo que la idea es buena para hacer una tabla de verdad para las condiciones para ver si algo es siempre o nunca cierto porque no tengo experiencia en este nivel detallado de puntero C hasta ahora, pero estoy trabajando en ello. Puede encontrar el repositorio completo [aquí] (https://github.com/ montao / openshell) para esta cosa que es mi propia shell similar a `sash` o` dash`. Es interesante que Valgrind pueda encontrar tanto sobre la RAM, escribí una prueba que usa Valgrind.
No es una herramienta automatizada, sino una forma estándar de escribir programas sin goto sin usar variables adicionales: http://stackoverflow.com/a/36661381/120163
El análisis no puede modificar su código. El análisis es una operación de solo lectura. Quizás familiarizarse con algunos conceptos primero, p. Ej. https://www.pluralsight.com/courses/brownfield Escriba pruebas unitarias, mida la cobertura del código o una mejor cobertura de sucursales. Luego busque un IDE que tenga métodos de refactorización como "Extraer método" e "Invertir instrucción if", algo como Jetbrains Resharper pero para C. Identifica errores lógicos como "Esta condición if siempre es verdadera".
Vuelva a escribir esto con clases (c ++) y polimorfismo y evite las ramificaciones. Http://stackoverflow.com/questions/519515/how-can-polymorphism-replace-an-if-else-statement-inside-of-a-loop
Consulte también esto: http://programmers.stackexchange.com/questions/47789/how-would-you-refactor-nested-if-statements y esto: http://www.drdobbs.com/architecture-and-design / refactoring-deep-nested-code / 231500074? pgno = 1
Podría considerar simplemente reorganizar el código para hacerlo legible. Ver http://stackoverflow.com/q/37079307/120163
@IraBaxter Sí. Mi IDE me dice cuando una condición es "siempre verdadera" y luego puedo eliminarla. Hoy estoy aprendiendo a hacer una gramática, pero no sé cómo implementar reglas. Intento esto con el analizador de limón para una palabra clave `while`` expr (A) :: = WHILE LPAR expr (B) RPAR expr (C). {printf ("prueba"); } `pero la cadena de prueba no se imprime. Mi código se volvió complicado y estoy tratando de hacer una gramática en su lugar.
Realmente, realmente no desea construir su propio analizador de C. Primero, es mucho más difícil de lo que parece, porque C es mucho más complicado de lo que crees, los compiladores no están de acuerdo con lo que es legal y tienes que hacer que el preprocesador sea correcto. Si logra hacer todo eso, su siguiente problema es que un analizador no es lo suficientemente bueno para hacer mucho con él. Vea mi ensayo en http://www.semanticdesigns.com/Products/DMS/LifeAfterParsing.html
@IraBaxter ¡Gracias Ira! Marqué los enlaces. Pero debo aprender a escribir gramática. Estoy aprendiendo el analizador de limón y casi puedo usar la palabra clave `while` basada en la calculadora simple. Ahora dicen en la revisión del código de hoy que mi código muestra "muchas" mejoras: http://codereview.stackexchange.com/questions/128149/running-shell-commands-in-a-pipeline
One responder:
rrirower
2016-05-04 20:44:54 UTC
view on stackexchange narkive permalink

He tenido cierto éxito con una herramienta llamada CppCheck a través de un sistema de CI de Jenkins. No realizo un seguimiento específico de las ramas condicionales, pero vale la pena ver la cantidad de cheques que proporciona esta herramienta. En particular, verifique la parte Condition que enumera varias verificaciones de condiciones que siempre son verdaderas / falsas (incluido el seguimiento de valores, condiciones duplicadas, lógica de intervalo), pero otras categorías también enumeran algunas verificaciones posiblemente útiles como:

  • "adición de puntero en condición"
  • "código idéntico en ambas ramas de if / else u operador ternario".
  • otros tipos de sospechosos o redundantes condiciones (para STL, cadenas, operaciones lógicas / numéricas, ...)

Y está disponible como un complemento para su IDE.

* “Código idéntico en ambas ramas de if / else u operador ternario”. * Wow. ¿Ese es el ejemplo más interesante de lo que puede hacer CppCheck? He estado codificando durante 45 años y nunca me he encontrado con esto.


Esta pregunta y respuesta fue traducida automáticamente del idioma inglés.El contenido original está disponible en stackexchange, a quien agradecemos la licencia cc by-sa 3.0 bajo la que se distribuye.
Loading...