Pregunta

Me acuerdo cuando estaba en algún curso de programación C, un profesor sugirió una vez que utilizo printf a ver la ejecución de un programa que yo estaba tratando de depurar. Este programa tuvo un fallo de segmentación con una causa que no puedo recordar en este momento. Seguí su consejo y el fallo de segmentación desapareció. Afortunadamente, un TA inteligente me dijo que depurar en lugar de utilizar printfs. En este caso, se trataba de una cosa útil que hacer.

Por lo tanto, hoy quería mostrar a alguien que el uso de printf potencialmente podría ocultar un error, pero no puedo encontrar ese viejo código que tenía este extraño error (función? Hmmm).

Pregunta: ¿Alguno de ustedes encontrado con este comportamiento, así? ¿Cómo podría reproducir algo como esto?

Editar

veo que mi pregunta parte orienta mi opinión "Uso printf está mal". No estoy diciendo que exactamente y no me gusta tomar opiniones extremas, por lo que estoy editando la pregunta un poco. Estoy de acuerdo que printf es una herramienta buena, pero sólo quería recrear un caso en el que printfs hacen un fallo de segmentación desaparecer y, por tanto, demostrar que hay que tener cuidado.

¿Fue útil?

Solución

Hay casos en que la adición de printf llama altera el comportamiento del código, pero también hay casos en los que la depuración hace lo mismo. El ejemplo más destacado es la depuración del código multiproceso, donde detener la ejecución de un subproceso puede alterar el comportamiento del programa, por lo que el insecto que busca puede no suceder.

Así que usando declaraciones printf sí tiene razones válidas. Ya sea para depurar o printf debe decidirse en una base de caso por caso. Tenga en cuenta que los dos no son de todos modos exclusivo - que puede código de depuración, incluso si contiene llamadas printf: -)

Otros consejos

tendría un momento muy difícil convencerme de no utilizar el registro (y printf en esta situación es un tenían hoc forma de registro) de depurar. Obviamente para depurar un accidente, lo primero es conseguir una traza inversa purificar y utilización o una herramienta similar, pero si la causa no es obvia la tala es por lejos una de las mejores herramientas que puede utilizar. Un depurador le permite concentrarse en los detalles, el registro le dará una imagen más grande. Ambos son útiles.

Parece que usted está tratando con un Heisenbug .

No creo que haya nada intrínsecamente "malo" con el uso de printf como una herramienta de depuración. Pero sí, al igual que cualquier otra herramienta, tiene sus defectos, y sí que ha habido más de un occaision donde la adición de printf creó un Heisenbug. heisenbug Sin embargo, también he tenido aparecen como resultado de la distribución de la memoria cambios introducidos por un depurador, en cuyo caso printf resultó fundamental en el seguimiento de los pasos que conducen a la caída.

En mi humilde opinión Todos los desarrolladores todavía se basa aquí y allá en las impresiones. Que acabamos de aprender a llamarlos "registros detallados".

Más al punto, el principal problema que he visto es que las personas se tratan printfs como si fueran invencibles. Por ejemplo, no es raro en Java para ver algo como

System.out.println("The value of z is " + z + " while " + obj.someMethod().someOtherMethod());

Esto es muy bueno, excepto que z fue realmente involucrado en el método, pero que no era otro objeto, y hay que asegurarse de que no obtendrá una excepción por la expresión de obj.

Otra cosa que hacen las impresiones es que introducen retrasos. He visto código con las condiciones de carrera a veces "se arreglen" cuando se introducen las impresiones. No me sorprendería si algunos usos de código que.

Me acuerdo una vez tratando de depurar un programa en el Macintosh (alrededor de 1991), donde el código de limpieza generada del compilador para un marco de pila entre 32K y 64K era errónea, ya que utiliza una adición de direcciones de 16 bits en lugar de 32 bits uno (una cantidad de 16 bits agrega a un registro de dirección será de signo extendido en el 68000). La secuencia era algo como:

  copy stack pointer to some register
  push some other registers on stack
  subtract about 40960 from stack pointer
  do some stuff which leaves saved stack-pointer register alone
  add -8192 (signed interpretation of 0xA000) to stack pointer
  pop registers
  reload stack pointer from that other register

El efecto neto fue que todo estaba bien excepto que los registros guardados estaban corrompidos, y uno de ellos tenía una constante (la dirección de una matriz global). Si el compilador optimiza una variable a un registro durante una sección de código, se informa que en el archivo de depuración-información para que el depurador puede emitida correctamente. Cuando una constante por lo que se optimiza, al parecer, el compilador no incluye dicha información, ya que no debería ser necesario. Localicé cosas por hacer un "printf" de la dirección de la matriz, y establecer puntos de interrupción para que pudiera ver la dirección antes y después de la printf. El depurador informó correctamente la dirección antes y después de la printf, pero el printf emite el valor incorrecto, por lo que el código desmontado y vio que estaba empujando printf A3 registro en la pila; visualización de registro A3 antes de la printf mostró que tenía un valor bastante diferente de la dirección de la matriz (el printf mostró el valor A3 en realidad llevó a cabo).

No sé cómo lo que jamás hubiera seguido que uno abajo si no hubiera sido capaz de utilizar tanto el depurador y juntos printf (o, para el caso, si yo no había entendido 68000 código ensamblador).

Me las arreglé para hacer esto. Estaba leyendo los datos desde un archivo plano. Mi algoritmo defectuoso fue como sigue:

  1. obtener la longitud del archivo de entrada en bytes
  2. asignar una matriz de longitud variable de caracteres para servir como un tampón
    • los archivos son pequeñas, así que no estoy preocupado por desbordamiento de pila, pero ¿qué pasa con los archivos de entrada de longitud cero? ¡Uy!
  3. devolverá un código de error si la longitud del archivo de entrada es 0

He descubierto que mi función sería tirar de forma fiable un fallo seg - a menos que hubiera un printf algún lugar en el cuerpo de la función, en cuyo caso podría funcionar exactamente como era mi intención. La corrección para la falla seg fue asignar la longitud del archivo más uno en el paso 2.

acabo de tener una experiencia similar. Aquí está mi problema específico, y la causa:

// Makes the first character of a word capital, and the rest small
// (Must be compiled with -std=c99)
void FixCap( char *word )
{
  *word = toupper( *word );
  for( int i=1 ; *(word+i) != '\n' ; ++i )
    *(word+i) = tolower( *(word+i) );
}

El problema es con la condición de bucle - Solía ??'\ n' en lugar del carácter nulo '\ 0'. Ahora, yo no sé exactamente cómo funciona printf, pero a partir de esta experiencia que supongo que utiliza algún lugar de memoria después de mis variables como espacio temporal / trabajo. Si un printf sentencia tiene como resultado una 'n \' carácter que se escriben en algún lugar donde se almacena después de mi palabra, entonces la función FixCap será capaz de detener en algún momento. Si quito el printf, a continuación, se mantiene en un bucle, en busca de un '\ n' pero nunca encontrarlo, hasta que segfaults.

Así que al final, la causa de mi problema es que a veces escribo '\ n' cuando me refiero a '\ 0'. Es un error que he hecho antes, y probablemente uno que va a hacer de nuevo. Pero ahora sé que buscarlo.

Bueno, tal vez usted podría enseñarle cómo usar GDB o de otros programas de depuración? Dile que si un error desaparece gracias a una juste "printf", entonces realmente no desaparecen y podría aparecer de nuevo este último. Un fallo debe ser fijo, no ignorado.

Esto le dará una división por 0 al retirar la línea printf:

int a=10;
int b=0;
float c = 0.0;

int CalculateB()
{
  b=2;
  return b;
}
float CalculateC()
{
  return a*1.0/b;
}
void Process()
{
  printf("%d", CalculateB()); // without this, b remains 0
  c = CalculateC();
}

¿Cuál sería el caso de depuración? Impresión de una serie char *[] antes de llamar exec() sólo para ver cómo se tokenized -. Creo que eso es un uso muy válida para printf()

Sin embargo, si el formato es alimentado a printf() del coste y la complejidad que en realidad puede alterar la ejecución del programa (velocidad, en su mayoría), un depurador puede ser el mejor camino a seguir suficiente. Por otra parte, depuradores y perfiladores también tienen un costo. O bien se pueden exponer carreras que podrían no aparecer en su ausencia.

Todo depende de lo que está escribiendo y el insecto que está persiguiendo. Las herramientas disponibles son depuradores, printf() (agrupación de registradores en printf también) afirmaciones y perfiladores.

Es un destornillador de punta mejor que otros tipos? Depende de lo que necesites. Tenga en cuenta, no estoy diciendo que las afirmaciones son buenas o malas. No son más que otra herramienta.

Una forma de lidiar con esto es para establecer un sistema de macros que hace que sea fácil para apagar printfs w / o tener que eliminarlos en su código. Utilizo algo como esto:

#define LOGMESSAGE(LEVEL, ...) logging_messagef(LEVEL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);

/* Generally speaking, user code should only use these macros.  They
 * are pithy. You can use them like a printf:
 *
 *    DBGMESSAGE("%f%% chance of fnords for the next %d days.", fnordProb, days);
 *
 * You don't need to put newlines in them; the logging functions will
 * do that when appropriate.
 */
#define FATALMESSAGE(...) LOGMESSAGE(LOG_FATAL, __VA_ARGS__);
#define EMERGMESSAGE(...) LOGMESSAGE(LOG_EMERG, __VA_ARGS__);
#define ALERTMESSAGE(...) LOGMESSAGE(LOG_ALERT, __VA_ARGS__);
#define CRITMESSAGE(...) LOGMESSAGE(LOG_CRIT, __VA_ARGS__);
#define ERRMESSAGE(...) LOGMESSAGE(LOG_ERR, __VA_ARGS__);
#define WARNMESSAGE(...) LOGMESSAGE(LOG_WARNING, __VA_ARGS__);
#define NOTICEMESSAGE(...) LOGMESSAGE(LOG_NOTICE, __VA_ARGS__);
#define INFOMESSAGE(...) LOGMESSAGE(LOG_INFO, __VA_ARGS__);
#define DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#if defined(PAINFULLY_VERBOSE)
#   define PV_DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#else
#   define PV_DBGMESSAGE(...) ((void)0);
#endif

logging_messagef() es una función definida en un archivo separado .c. Usar la xmessage (...) las macros en su código dependiendo del propósito del mensaje. Lo mejor de esta configuración es que funciona para depuración y registro, al mismo tiempo, y la función logging_messagef() se puede cambiar para hacer varias cosas diferentes (printf salida estándar de errores, a un archivo de registro, syslog uso o alguna otra función de registro del sistema, etc.), y los mensajes por debajo de un cierto nivel puede ser ignorado en logging_messagef() cuando no los necesita. PV_DBGMESSAGE() es para aquellos mensajes de depuración copiosas que sin duda tendrá que desactivar en la producción.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top