Red de conocimiento informático - Conocimiento informático - ¿Cuál es la mejor manera de depurar C?

¿Cuál es la mejor manera de depurar C?

Para comprender cuál es la mejor forma de depurar un programa, primero debemos analizar los tres elementos del proceso de depuración:

¿Qué herramientas se deben utilizar para depurar un programa?

Uso ¿Cómo puedo encontrar errores en un programa?

¿Cómo puedo evitar errores desde el principio?

¿Qué herramientas debo utilizar para depurar un programa?

Los programadores experimentados utilizan muchas herramientas para ayudar a depurar programas, incluido un conjunto de depuradores y algunos programas "lint" y, por supuesto, el compilador en sí también es una herramienta de depuración.

Los depuradores son particularmente útiles a la hora de comprobar errores lógicos en programas, por lo que muchos programadores utilizan los depuradores como herramienta básica de depuración. En términos generales, los depuradores pueden ayudar a los programadores a completar las siguientes tareas:

(1) Observar el estado de ejecución del programa

Esta función por sí sola hace que un depurador típico sea incomparable Valor estimado. Incluso si pasas meses escribiendo cuidadosamente un programa, no necesariamente sabes exactamente cómo funciona en cada paso del camino. Si el programador olvida algunas declaraciones if, llamadas a funciones o procedimientos de bifurcación, es posible que se omitan o ejecuten ciertas secciones del programa, y ​​este resultado no es el que esperaba el programador. En cualquier caso, durante la ejecución del programa, especialmente cuando el programa se comporta de manera anormal, si el programador puede verificar en cualquier momento qué líneas de código se están ejecutando actualmente, entonces podrá comprender bien lo que está haciendo el programa y el aparición de errores.

(2) Establecer puntos de interrupción

Al establecer puntos de interrupción, el programa se puede detener temporalmente cuando la ejecución alcanza un punto determinado. Este enfoque es particularmente útil cuando sabes en qué parte del programa ocurrió el error. Puede establecer puntos de interrupción antes, en el medio o después de la sección problemática del programa. Cuando el programa alcanza un punto de interrupción, se detendrá temporalmente y podrá verificar los valores de todas las variables locales, parámetros y variables globales. Si todo está bien, puede continuar ejecutando el programa hasta llegar a otro punto de interrupción o hasta que se revele la causa del problema.

(3) Monitoreo de conjuntos

Los programadores pueden monitorear una variable a través del depurador, es decir, monitorear continuamente el valor o contenido de una variable. Si conoce el rango de valores o el contenido válido de una variable, podrá descubrir rápidamente la causa del error de esta manera. Además, puede hacer que el depurador supervise las variables por usted y detenga la ejecución del programa si una variable queda fuera de un rango de valores predefinido o si se cumple una determinada condición. Esto es conveniente si conoce todo el comportamiento de las variables.

Los buenos depuradores suelen proporcionar otras funciones para simplificar la depuración. Sin embargo, los depuradores no son las únicas herramientas de depuración de los programas Lint y los propios compiladores también pueden proporcionar medios valiosos para analizar el funcionamiento de un programa.

Nota: El programa lint puede identificar cientos de errores de programación comunes e informar en qué parte del programa ocurren estos errores. Si bien algunos de estos no son realmente errores, la mayoría son valiosos.

Una función típica proporcionada por los programas y compiladores lint son las comprobaciones en tiempo de compilación, que no están disponibles en los depuradores. Cuando utilice estas herramientas para compilar su programa, identificarán secciones problemáticas de su programa, secciones que pueden tener efectos inesperados y errores comunes. A continuación analizaremos varios ejemplos de aplicación de este método de inspección, que creo que le serán de utilidad.

Uso indebido del operador de igualdad

La comprobación en tiempo de compilación puede ayudar a detectar el uso indebido del operador de igualdad. Mire el siguiente segmento del programa:

void foo(int a, int b)

{

if ( a = b )

{

/ *algo de código aquí * /

}

}

¡Este tipo de error generalmente es difícil de encontrar! Programa En lugar de comparar las dos variables, el valor de b se asigna a a, y el cuerpo if se ejecuta si b no es cero. En términos generales, esto no es lo que quieren los programadores (aunque es posible).

De esta manera, no solo el segmento de programa relevante se ejecutará un número incorrecto de veces, sino que el valor de la variable a también será incorrecto cuando se use más adelante.

Variables no inicializadas

Las comprobaciones en tiempo de compilación ayudan a encontrar variables no inicializadas. Mire la siguiente función:

promedio vacío (float ar[], int size)

{

float total;

int a;

for( a = 0;a

{

total+=ar[a];

}

printf(" %f\n", tamaño total / (flotante) );

}

El problema aquí es que la variable El total no está inicializado, por lo que es probable que sea un número aleatorio e inútil. La suma de los valores de todos los elementos de la matriz se sumará al valor de este número aleatorio (esta parte del programa es correcta), y luego se generará el promedio del conjunto de números que incluye este número aleatorio.

Conversión de tipo implícita de variables

En algunos casos, el lenguaje C convertirá automáticamente variables de un tipo a otro tipo. Esto puede ser algo bueno (el programador ya no tiene que hacer este trabajo), pero también puede tener efectos no deseados. La conversión implícita de un tipo de puntero a un número entero es probablemente la peor conversión de tipo implícita.

void sort( int ar[],int size )

{

/* el código para ordenar va aquí * /

}

int main()

{

int argy[10];

sort( 10, matriz );

}

El programa anterior obviamente no es lo que el programador esperaba. Aunque sus resultados reales de ejecución son difíciles de predecir, es sin duda catastrófico.

¿Qué método se puede utilizar para encontrar errores en el programa?

En el proceso de depuración del programa, los programadores deben recordar las siguientes habilidades:

Primera depuración partes más pequeñas de su programa y luego depurar partes más grandes

Si su programa está bien escrito, contendrá algunas partes más pequeñas y es mejor verificarlas primero. Aunque los errores en el programa no ocurren necesariamente en estas partes, depurarlos primero puede ayudarlo a comprender la estructura general del programa y verificar qué partes del programa están libres de errores. Además, cuando depura los componentes más grandes de su programa, puede estar seguro de que los componentes más pequeños funcionan correctamente.

Es muy importante depurar minuciosamente un componente del programa antes de depurar el siguiente

Si se demuestra que un componente de un programa es correcto, no sólo se reduce el alcance de posibles errores, sino que otros componentes del programa pueden utilizar esa parte del programa de forma segura. Aquí se aplica una buena regla general: en pocas palabras, la dificultad de depurar un fragmento de código es proporcional al cuadrado de la longitud del código. Por lo tanto, depurar un fragmento de código de 20 líneas es 4 veces más difícil que depurar uno de 10 líneas. pieza de línea de código. Por lo tanto, resulta útil centrarse en una pequeña sección de código a la vez durante la depuración. Por supuesto, esto es sólo un principio general y el uso específico dependerá de la situación específica.

Observar continuamente los cambios en el flujo y los datos del programa.

Esto también es importante. Si diseña y escribe su programa con cuidado, al monitorear la salida del programa podrá saber exactamente qué parte. del código que se está ejecutando y cuáles son los contenidos de cada variable. Por supuesto, no puedes hacer esto si el programa se comporta incorrectamente. Para hacer esto, generalmente solo puede utilizar la depuración del programa o agregar una gran cantidad de declaraciones impresas al programa para observar el flujo de control y el contenido de variables importantes.

Active siempre la opción de advertencia del compilador e intente eliminar todas las advertencias

En el proceso de desarrollo de un programa, debe hacer esto de principio a fin, de lo contrario, enfrentará un problema muy serio. Aunque muchos programadores consideran que eliminar las advertencias del compilador es una tarea tediosa, puede resultar valiosa. La mayor parte del código sobre el que el compilador da advertencias es al menos problemático, por lo que vale la pena dedicar algo de tiempo a convertirlo en código correcto y, al eliminar estas advertencias, a menudo encontrará el lugar del programa donde realmente ocurrió el error;

Reducir con precisión el alcance del error

Si puede determinar inmediatamente la parte del programa donde existe el error y encontrarlo allí, ahorrará mucho tiempo de depuración. , y podrás convertirte en un depurador profesional con unos ingresos bastante altos. Pero, de hecho, no siempre podemos alcanzar el objetivo de inmediato, por lo que el enfoque habitual es reducir gradualmente el alcance del programa que puede tener errores y, a través de este proceso, descubrir la parte del programa que realmente contiene errores. . No importa lo difícil que sea encontrar el error, este enfoque siempre funciona. Cuando encuentre esta parte del programa, podrá centrar todo su trabajo de depuración en esta parte del programa. No hace falta decir que es importante limitar el alcance con precisión; de lo contrario, es probable que la parte del programa en la que termine centrándose en depurar sea completamente correcta.

¿Cómo evitar errores desde el principio?

Hay un proverbio: "cortar de raíz", lo que significa que es mejor evitar los problemas que encontrar soluciones después de que hayan aparecido. ocurrir. Es mucho mejor hacer las paces. ¡Esto también es cierto en la programación de computadoras! Al escribir un programa, un programador experimentado dedica más tiempo y energía que un programador sin experiencia, pero es este estilo de programación paciente y riguroso el que los programadores experimentados a menudo solo necesitan dedicar un poco de tiempo a depurar el programa. y si el programa necesita resolver un problema o realizar algunos cambios más adelante, puede corregir rápidamente el error y agregar el código correspondiente. Por el contrario, incluso si un programa de mala calidad es generalmente correcto, puede ser una pesadilla cambiarlo o corregir uno de los errores que pronto quedarán expuestos.

En términos generales, los programas escritos según principios de programación estructurada son fáciles de depurar y modificar. Algunos de estos principios se presentarán a continuación.

Debería haber suficientes comentarios en el programa

Algunos programadores piensan que comentar el programa es una tarea tediosa, pero incluso si nunca piensas en dejar que otros lean tu programa, deberías También agregue suficientes comentarios a su programa, porque incluso las declaraciones que cree que son claras y obvias ahora a menudo se volverán oscuras unos meses después. Esto no quiere decir que más comentarios sean mejores. A veces, demasiados comentarios pueden confundir el significado original del código. Sin embargo, es necesario agregar algunas líneas de comentarios dentro de cada función y antes del código que realiza funciones importantes o que no es inmediatamente obvio.

Aquí hay un fragmento de código bien comentado:

/*

* Calcular un valor factorial entero usando recursividad.

* Ingresar un número entero.

* Salida: otro número entero

* Efectos secundarios: puede hacer estallar la pila si el valor de entrada es * Enorme *

*/

int factorial (int número)

{

if (número < = 1)

devuelve 1; /* El factorial de uno es uno; p>

else

return n * factorial( n - 1 );

/ * ¡La magia! Esto es posible gracias al factorial de a

número es el número mismo multiplicado por el factorial del

número menos uno. * /

}

Las funciones deben ser concisas.

De acuerdo con el principio mencionado anteriormente (la dificultad de depurar un fragmento de código es proporcional al cuadrado de la longitud del código), sin duda es beneficioso escribir funciones de manera concisa. Sin embargo, es necesario agregar que si una función es muy concisa, debes dedicar un poco más de tiempo a analizarla y verificarla cuidadosamente para asegurarte de que sea precisa. Luego podrá continuar escribiendo el resto de su programa y estar tan seguro de que la función que acaba de escribir es correcta que nunca necesitará volver a verificarla. A menudo no se tiene esta confianza en una rutina larga y compleja.

Otro beneficio de escribir funciones breves y concisas es que después de escribir una función corta, puedes usarla en otras partes del programa. Por ejemplo, si está escribiendo un programa de procesamiento financiero, es posible que necesite calcular el interés trimestral, mensual, semanal o día del mes en diferentes partes del programa. Si el programa está escrito según el principio no estructurado, entonces se requiere un fragmento de código separado para cada punto donde se calcula el interés. Estos códigos repetidos harán que el programa sea largo y difícil de leer.

Sin embargo, puedes simplificar la implementación de estas tareas en una función como esta:

/*

* ComDllte cuál sería la tasa de interés "real"

* para una tasa de interés fija determinada, dividida en N segmentos

*/

interés calculado doble (tasa doble, segmentos int)

{

int a;

resultado doble = 1.0;

Tasa /= (doble) Segmentos;

for( a = 0; a< Segmentos ; ++a )

Resultado * = Tasa;

devolver resultado;

}

Después de escribir la función anterior, puede Llame a esta función en todos los lugares donde calcule el interés. De esta manera, no solo puede eliminar eficazmente los errores en cada fragmento de código copiado, sino también acortar en gran medida la duración del programa y simplificar la estructura del programa. Esta técnica también suele hacer que otros errores en el programa sean más fáciles de encontrar.

Cuando te acostumbres a utilizar este método para descomponer el programa en módulos controlables, descubrirás que tiene usos más maravillosos.

El flujo del programa debe ser claro y se debe evitar el uso de declaraciones goto y otras declaraciones de salto

Este principio ha sido ampliamente aceptado en el campo de la tecnología informática, pero en algunos círculos Todavía no está claro. Sin embargo, también se acepta en general que los programas que permiten que el flujo del programa omita incondicionalmente partes del código con unas pocas declaraciones son mucho más fáciles de depurar porque dichos programas son generalmente más claros y fáciles de entender. Muchos programadores no saben cómo reemplazar esos "saltos no estructurados" con estructuras de programas estructurados. Aquí hay algunos ejemplos de cómo se debe hacer esto:

for( a = 0; a<100s ++a)

{

Func1( a );

si (a == 2 ) continúa;

Func2( a );

}

Cuando a es igual a 2, este programa omite la parte restante del ciclo a través de la instrucción continuar. Se puede reescribir de la siguiente manera:

for( a = 0; a<100; ++a)

{

Func1 (a); p>

if (a !=2 )

Func2(a) ;

}

Este programa es más fácil de depurar porque toma el código dentro de los corchetes se muestra claramente lo que se debe y no se debe hacer. Entonces, ¿cómo hace que su código sea más fácil de modificar y depurar? Supongamos que desea agregar algún código que se ejecutará al final de cada ciclo. En el primer ejemplo, si observa la instrucción continuar, debe hacerlo complejo. modificaciones a este programa (también puedes probarlo, ¡porque no es obvio!) Si no notas la declaración continuar, entonces puedes cometer un error que es difícil de encontrar. En el segundo ejemplo, el cambio es simple: simplemente agrega el nuevo código al final del cuerpo del bucle.

Puede ocurrir otro error al utilizar la declaración break.

Supongamos que escribe el siguiente programa:

for (a =0) a<100 ++a)

{

if (Func1 (a ) = =2 )

break;

Func2 (a) ;

}

Supongamos que el valor de retorno de la función Funcl() es siempre Si no es igual a 2, el ciclo anterior continuará de 1 a 100; de lo contrario, el ciclo finalizará antes de llegar a 100; Si desea agregar código al cuerpo de un bucle y ve dicho cuerpo de bucle, probablemente pensará que de hecho puede realizar un bucle de 0 a 99, y esta suposición puede hacer que cometa un error peligroso. Otro peligro puede surgir del uso de un valor, porque cuando finaliza el ciclo, el valor de a no es necesariamente 100.

El lenguaje C puede ayudarte a resolver este problema. Puedes escribir este bucle for de la siguiente forma:

for(a=O; a<100&&Func1(a)!=2; ++a)

El bucle anterior le dice claramente al programador: "Haga un bucle de 0 a 99, pero deténgalo una vez que Func1() sea igual a 2". Debido a que toda la condición de salida es muy clara, será difícil para los programadores cometer los errores mencionados anteriormente.

Los nombres de funciones y variables deben ser descriptivos

El uso de nombres descriptivos de funciones y variables expresa el significado del código con mayor claridad y, hasta cierto punto, esto en sí mismo es una especie de comentario. Los siguientes ejemplos son los mejores ejemplos:

y=p+i-c;

y

SumaAnual=Principal+Intereses-Cargos:

¿Cuál es más claro?

p=*(l+o);

y

page=&List[offset];

¿Cuál es más claro?