El progtwig C se bloquea al agregar un int extra

Soy nuevo en C y uso Eclipse IDE El siguiente código funciona bien:

#include  #include  #include  int main() { char *lineName; int stationNo; int i; while (scanf("%s (%d)", lineName, &stationNo)!=EOF) { for (i=0; i<5 ; i++ ){ printf("%d %d",i); } } return 0; } 

Entrada:

 Green (21) Red (38) 

Salida:

 Green (21) Red (38) 0123401234 

Sin embargo, cuando simplemente agregue un nuevo int:

 #include  #include  #include  int main() { char *lineName; int stationNo; int i,b=0; while (scanf("%s (%d)", lineName, &stationNo)!=EOF) { printf("%d",b); for (i=0; i<5 ; i++ ){ printf("%d",i); } } return 0; } 

El progtwig se bloqueará con la misma entrada. ¿Puede alguien decirme por qué?

Usted dijo que su primer progtwig “funciona”, pero funciona solo por accidente. Es como un auto que avanza por la carretera sin lugnuts en las ruedas delanteras, solo por un milagro que no se han caído, todavía.

Tu dijiste

 char *lineName; 

Esto le da una variable de puntero que puede apuntar a algunos caracteres, pero aún no apunta a ningún lado . El valor de este puntero no está definido. Es como decir ” int i ” y preguntar cuál es el valor de i .

Luego dijiste

 scanf("%s (%d)", lineName, &stationNo) 

Le está pidiendo a scanf que lea el nombre de una línea y almacene la cadena en la memoria apuntada por lineName . Pero ¿dónde está esa memoria? No tenemos idea alguna!

La situación con los punteros sin inicializar es un poco más difícil de pensar porque, como siempre, con los punteros tenemos que distinguir entre el valor del puntero en lugar de los datos en la memoria a la que apunta el puntero . Anteriormente mencioné decir int i y preguntar cuál es el valor de i . Ahora, habrá un patrón de bits en i , puede ser 0, 1, -23 o 8675309.

De manera similar, habrá un patrón de bits en la línea lineName , que podría “apuntar a” la ubicación de memoria 0x00000000, o 0xffe01234, o 0xdeadbeef. Pero las preguntas son: ¿hay realmente memoria en esa ubicación y tenemos permiso para escribir en ella? ¿Se está utilizando para otra cosa? Si hay memoria y tenemos permiso y no se está utilizando para nada más, el progtwig podría funcionar, por ahora. ¡Pero esos son tres grandes si! Si la memoria no existe, o si no tenemos permiso para escribir en ella, el progtwig probablemente se bloquee cuando lo intente. Y si la memoria se está utilizando para otra cosa, algo va a salir mal cuando le pedimos a scanf que escriba una cadena allí.

Y, realmente, si lo que nos importa es escribir progtwigs que funcionen (y que funcionen por las razones correctas), no tenemos que hacer ninguna de estas preguntas. No tenemos que preguntar dónde señala lineName cuando no lo inicializamos, si hay memoria allí, o si tenemos permiso para escribir, o si se está utilizando para otra cosa. En su lugar, simplemente deberíamos inicializar lineName ! ¡Debemos hacer que apunte explícitamente a la memoria que poseemos y que podemos escribir y eso no se usa para nada más!

Hay varias formas de hacerlo. Lo más fácil es usar una matriz para lineName , no un puntero:

 char lineName[20]; 

O, si tenemos el corazón puesto en usar un puntero, podemos llamar a malloc :

 char *lineName = malloc(20); 

Sin embargo, si lo hacemos, tenemos que verificar que malloc tenga éxito:

 if(lineName == NULL) { fprintf(stderr, "out of memory!\n"); exit(1); } 

Si realiza cualquiera de esos cambios, su progtwig funcionará.

… Bueno, en realidad, todavía estamos en una situación en la que su progtwig parecerá funcionar, a pesar de que todavía tiene otro problema bastante serio y oculto. Hemos asignado 20 caracteres para lineName , lo que nos da 19 caracteres reales, más el '\0' al final. Pero no sabemos qué va a escribir el usuario. ¿Qué pasa si el usuario escribe 20 o más caracteres? Eso hará que scanf escriba 20 o más caracteres en lineName , pasado el final de lo que la memoria de lineName puede guardar, y estamos de vuelta en la situación de escribir en la memoria que no nos lineName y que podría ser en uso para otra cosa.

Una solución es hacer que lineName más grande: declararlo como char lineName[100] , o llamar a malloc(100) . Pero eso solo mueve el problema: ahora tenemos que preocuparnos por la posibilidad (quizás menor) de que el usuario escriba 100 o más caracteres. Entonces, lo siguiente que debe hacer es decirle a scanf no escriba más en lineName de lo que hemos dispuesto para que se mantenga. En realidad, esto es bastante simple: si lineName todavía está configurado para contener 20 caracteres, simplemente llame

 scanf("%19s (%d)", lineName, &stationNo) 

El especificador de formato %19s le dice a scanf que solo se le permite leer y almacenar una cadena de hasta 19 caracteres.


Ahora, he dicho muchas cosas aquí, pero me doy cuenta de que en realidad no he llegado a contestar la pregunta de por qué su progtwig pasó de funcionar a fallar cuando realizó ese cambio aparentemente trivial y aparentemente no relacionado. Esto termina siendo una pregunta difícil de responder satisfactoriamente. Volviendo a la analogía con la que comencé, es como preguntar por qué pudo conducir el auto sin lugnuts a la tienda sin ningún problema, pero cuando intentó ir a la casa de la abuela, las ruedas se cayeron y se estrelló contra una zanja. . Hay un millón de factores posibles que podrían haber entrado en juego, pero ninguno de ellos cambia el hecho subyacente de que conducir un automóvil con las ruedas no atadas es una idea loca, no está garantizado que funcione en absoluto.

En su caso, las variables de las que está hablando ( lineName , lineName , i , y luego b ) son todas variables locales, normalmente asignadas en la stack. Ahora, una de las características de la stack es que se usa para todo tipo de cosas, y nunca se aclara entre usos. Entonces, si tiene una variable local sin inicializar, los bits aleatorios particulares que contiene dependen de lo que haya usado esa pieza de la stack la última vez. Si cambia su progtwig ligeramente para que se llamen a diferentes funciones, esas diferentes funciones pueden dejar diferentes valores aleatorios en la stack. O si cambia su función para asignar diferentes variables locales, el comstackdor puede colocarlas en diferentes lugares de la stack, lo que significa que terminarán recogiendo diferentes valores aleatorios de lo que haya habido la última vez.

De todos modos, de alguna manera, con la primera versión de su progtwig, lineName terminó conteniendo un valor aleatorio que correspondía a un puntero que apuntaba a la memoria real en la que podía escribir. Pero cuando agregó esa cuarta variable b , las cosas se movieron lo suficiente como para que lineName terminó siendo un puntero a la memoria que no existía o que no tenía permiso para escribir, y su progtwig se bloqueó.

¿Tener sentido?


Y ahora, una cosa más, si todavía estás conmigo. Si te detienes y piensas, todo esto podría ser un poco inquietante. Tenías un progtwig (tu primer progtwig) que parecía funcionar bien, pero en realidad tenía un error bastante horrible. Escribió al azar, memoria no asignada. Pero cuando lo comstackste no recibiste mensajes de error fatales, y cuando lo ejecutaste no hubo indicios de que algo estuviera mal. ¿Que pasa con eso?

La respuesta, como un par de comentarios aludidos, involucra lo que llamamos comportamiento indefinido .

Resulta que hay tres tipos de progtwigs de C, que podríamos llamar buenos, malos y feos.

  • Los buenos progtwigs funcionan por las razones correctas. No rompen ninguna regla, no hacen nada ilegal. No reciben advertencias ni mensajes de error cuando los comstack, y cuando los ejecuta, simplemente funcionan.

  • Los progtwigs defectuosos infringen alguna regla, y el comstackdor detecta esto, emite un mensaje de error fatal y se niega a producir un progtwig dañado para que lo intentes ejecutar.

  • Pero luego están los progtwigs feos, que se involucran en un comportamiento indefinido . Estos son los que rompen un conjunto diferente de reglas, los que, por diversas razones, el comstackdor no está obligado a quejarse. (De hecho, el comstackdor puede o no ser capaz de detectarlos). Y los progtwigs que se involucran en un comportamiento indefinido pueden hacer cualquier cosa .

Pensemos en esos dos últimos puntos un poco más. El comstackdor no está obligado a generar mensajes de error cuando escribe un progtwig que utiliza un comportamiento indefinido, por lo que podría no darse cuenta de que lo ha hecho. Y el progtwig tiene permitido hacer cualquier cosa, incluido el trabajo que usted espera. Pero entonces, ya que está permitido hacer cualquier cosa, podría dejar de funcionar mañana, aparentemente sin ninguna razón, ya sea porque hiciste un cambio aparentemente inocuo, o simplemente porque no estás cerca para defenderlo, ya que funciona silenciosamente. Desconecta y borra todos los datos de tus clientes.

Entonces, ¿qué se supone que debes hacer al respecto?

Una cosa es usar un comstackdor moderno si puede, y activar sus advertencias, y prestarles atención. (Los buenos comstackdores incluso tienen una opción llamada “tratar las advertencias como errores”, y los progtwigdores que se preocupan por los progtwigs correctos generalmente activan esta opción). Aunque, como dije, no están obligados a hacerlo, los comstackdores están mejorando cada vez más detectar un comportamiento indefinido y advertirle sobre ello, si se lo solicita.

Y luego la otra cosa, si vas a hacer mucha progtwigción en C, es tener cuidado de aprender el idioma, lo que puedes hacer, lo que no debes hacer. Haga un punto de escribir progtwigs que funcionen por las razones correctas . No se conforme con un progtwig que simplemente parece funcionar. Y si alguien señala que dependes de un comportamiento indefinido, no digas: “Pero mi progtwig funciona, ¿por qué debería importarme?” (No dijiste esto, pero algunas personas lo hacen).