Por que no me funciona este simple codigo?

Iniciado por CelDavid, 9 Mayo 2019, 01:44 AM

0 Miembros y 1 Visitante están viendo este tema.

CelDavid

Hola, si me pueden ayudar, debe ser simple el error, estaba aprendiendo C y resulta que hay una sentencia aqui comentada que si la descomento no me funciona, no entiendo por que? si me pueden ayudar gracias


#include <stdio.h>
#include <string.h>
#include <conio.h>
#include <stdlib.h>


struct pruebachar{
   char* cadenatres;
   int tel;
};

int main(){
   char *cadenauno="hola";
   char *cadenados;
   
   struct pruebachar var;
   struct pruebachar *pp;

   pp=&var;
   var.cadenatres="hola";

   printf("Ingrese esta palabra: hola\n");
   fflush(stdin);
   gets(cadenados);

   
   //printf("pp->cadenatres %s\n", pp->cadenatres);

   if(strcmp(cadenauno,cadenados)==0){
      printf("Son iguales\n");
   }else{
      printf("Son distintos\n");
   }

   if(strcmp(cadenauno,var.cadenatres)==0){
      printf("Son iguales\n");
   }else{
      printf("Son distintos\n");
   }


   if(strcmp(cadenauno,pp->cadenatres)==0){
      printf("Son iguales\n");
   }else{
      printf("Son distintos\n");
   }

   return 0;
}

K-YreX

Buenas, lo primero de todo si puedes poner el código anterior entre etiquetas de código GeSHi (encima del cuadro de texto hay un desplegable que pone "Código GeSHi" donde puede seleccionar el lenguaje de programación correspondiente).

Tu problema no está en la línea que has comentado, esa sentencia es correcta. El problema lo tiene <cadenados>, te comento.
Imaginemos la memoria como cajitas, en cada una cabe un valor. Por ejemplo:

char x = 'a'; // seria una cajita que guarda el contenido de <x> o sea, la 'a'.

Un array/arreglo son un conjunto de cajitas; en las que en cada una de ellas, se guarda un valor, por ejemplo:

char arregloDeChar[SIZE]; //serian un numero de cajitas igual al valor SIZE y en cada una cabe un <char>

Muchas veces se confunde un array con un puntero, pero un puntero únicamente es una cajita que en vez de guardar un dato directamente, guarda la dirección de memoria de otro dato, por ejemplo:

char x = 'a'; // supongamos que la direccion de x es 0x1 entonces en la posicion de memoria 0x1 se almacena 'a' (dicho de forma simple)
char *px = &x; // aqui creamos otra cajita para px que almacena la direccion de x. Imaginemos que px se encuentra en 0x2, entonces en 0x2 se guarda 0x1.


La confusión entre punteros y arrays se debe a lo siguiente:

char array[10] = {'a', 'b', 'c'};
// array[0] es un dato de tipo char, array[0] = 'a'
// array[1] es un dato de tipo char, array[1] = 'b'
// array[2] es un dato de tipo char, array[2] = 'c'
// array es un puntero a array[0]

Entonces cuando se trabaja con memoria dinámica (cosa que si estás empezando dudo que conozcas) lo que se hace es declarar un puntero y luego a ese puntero se le reserva memoria para que pase de tener 1 cajita de memoria a tener más. Si trabajas con memoria dinámica lo que debes usar son arrays estáticos, es decir, con los corchetes como he hecho yo arriba.
Aquí es donde aparece tu problema. Estás guardando una cadena mediante la función <gets()> (la cual te recomiendo que cambies por <fgets()>, no se recomienda usar <gets()> porque es menos segura) al puntero <cadenados>, pero <cadenados> solo tiene 1 cajita para un caracter.

Y puede que te preguntes "por qué con <cadenados> tengo ese problema y con <cadenauno> y <cadenatres> no?"
Verás, al poner por ejemplo:

char *cadenauno = "hola";

Lo que sucede es que al cargar el programa en memoria para ser ejecutado hay un "array" (entiéndase así) en el que el programa tiene guardado "hola" para poder asignarlo a <cadenauno>. Como ese "hola" ya está guardado en una posición de memoria, el programa lo único que hace es guardar en <cadenauno> la dirección de memoria en la que está guardado "hola" (la dirección en la que comienza "hola").
Es por esto que <cadenauno> y <cadenatres> no dan problemas. Porque la cadena de caracteres "hola" ya existe en memoria. Sin embargo, <cadenados> como la introduce el usuario por teclado, no existe en memoria antes del tiempo de ejecución.

Solución: Declarar un array. Y si luego quieres usar otro puntero pues lo creas y haces que apunte al comienzo del array.

#define SIZE 100 // longitud suficiente para que quepa lo que queremos guardar
char cadena2Array[SIZE];
printf("Introduce el valor de cadena2: ");
fgets(cadena2Array, SIZE, stdin); //  mas seguro que gets() porque se asegura de no pasarse del limite SIZE
printf("El valor de cadena2Array es: %s", cadena2Array);

// si quieres otro puntero
char *cadena2Puntero = cadena2Array;
printf("El valor de cadena2Puntero es: %s", cadena2Puntero); // Misma salida ya que apunta al mismo sitio


Te dejo AQUÍ un enlace a otro tema reciente en el que se tratan también estos temas y tienen algunas explicaciones más completas.
Espero que te sirva.
Suerte :-X
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

CelDavid

#2
Aaaa excelente explicacion!!!! ahora me funciona, muchas gracias!!!! una ultima duda, el problema era cadenados, pero este codigo me funcionaba igual
if(strcmp(cadenauno,cadenados)==0){
     printf("Son iguales\n");
  }else{
     printf("Son distintos\n");
  }
era por casualidad ya que quedaban cosas en el bufer? algo asi puede ser? porque si mando este codigo antes funciona (siempre que tenga comentado lo de cadenatres): printf("cadenados:%s\n", cadenados);  

K-YreX

No sé cómo te funcionaría pero a mí no me funciona. Sale un error por no tener memoria para <cadenados>.
Siempre que estés escribiendo en <cadenados> tiene que darte ese error (violación de segmento / segmentation fault). Otra cosa es si quitas también la parte en la que pides introducir "hola" en <cadenados>.

PD: No uses la librería <conio.h>, no es una librería estándar.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

CelDavid

Ok, muchas gracias por los consejos!!!  ;D

@XSStringManolo

Cita de: CelDavid en  9 Mayo 2019, 16:48 PM
Aaaa excelente explicacion!!!! ahora me funciona, muchas gracias!!!! una ultima duda, el problema era cadenados, pero este codigo me funcionaba igual
if(strcmp(cadenauno,cadenados)==0){
     printf("Son iguales\n");
  }else{
     printf("Son distintos\n");
  }
era por casualidad ya que quedaban cosas en el bufer? algo asi puede ser? porque si mando este codigo antes funciona (siempre que tenga comentado lo de cadenatres): printf("cadenados:%s\n", cadenados);  
Claro que te funcionaba teniendo comentado cadetres porque strcmp es la funcion de stream compare. Compara los caracteres de ambas cadenas 1 por 1. Si coinciden devuelve 0.
Si devuelve menos que cero significa que el puntero hacia el primer caracter que no coincide contiene un valor menor que el contenido alojada dentro de la memoria apuntada por el puntero de la segunda cadena indicada en la funcion. Los valores comparados son los valorea Ascii (por defecto) de los caracteres comparados. en caso de ambos ser minusculas o ambos ser mayusculas podrias deducir que alfabéticamente la cadena 1 iria ordenada antes que la cadena 2.
En caso de que el valor sea 0 ambos coinciden como es el caso de tu codigo si el usuario del ejecutable introduce "hola".
En caso de que la funcion devuelva un numero mayor que 0 el valor es superior.

Muy útil para ordenar alfabéticamente varias cadenas.

Si quieres que tu programa no sea case-sensitive en C tienes Ctype https://es.m.wikipedia.org/wiki/Ctype.h

K-YreX

Cita de: string Manolo en  9 Mayo 2019, 18:39 PM
En caso de que el valor sea 0 ambos coinciden como es el caso de tu codigo si el usuario del ejecutable introduce "hola"
Eso está claro, el problema es que si intentas guardar la cadena que el usuario introduce en <cadenados> cuando no se ha reservado memoria para ello, se genera un error en tiempo de ejecución por lo que no se producen las salidas que deberían.
En mi caso al menos me genera una segmentation fault aunque me suena que en ocasiones en C se permitía escribir en espacios de memoria que no habían sido reservados si estos espacios no superaban los límites de memoria del programa. De todas formas sigue sin ser correcto...
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

@XSStringManolo


RayR

#8
Creo que ya te explicaron bastante bien cuál era el problema, pero por si te queda la duda de por qué comentando esa línea específica el programa te funcionaba, te digo una posible causa, que además es una buena oportunidad para que veas por qué puede ser tan complicado detectar errores en el manejo de la memoria.

Cuando un programa se ejecuta, se reserva cierto espacio de memoria para él. En una parte se coloca el código del programa, en otras los datos, (como las variables y otro tipo de información), y algo de memoria extra que reservan como previsión (por si acaso más adelante hace falta, es más eficiente tenerla ya lista). Toda esa memoria "pertenece" a tu programa. Aparte de esto, el programa puede establecer permisos para ciertas zonas de la memoria, por ejemplo, el código del programa se suele colocar en un segmento de memoria marcado como de sólo lectura. Los errores de violación de acceso/segmento se dan cuando un proceso -o programa en ejecución- intenta acceder a memoria que no le pertenece, o bien, intenta hacer algo indebido con memoria que le pertence (por ejemplo, escribir en regiones de sólo lectura), pero para simplificar, me enfoco en el primer caso.

Cuando tienes un puntero sin inicializar, como cadenados, originalmente contendrá "basura", es decir, apunta a una dirección de memoria impredecible. Si intentas acceder a ella, y se trata de una dirección que tu programa no ha reservado, se producirá el fallo. Pero si por casualidad apunta a una dirección que sí le pertenece a tu programa, puede que funcione sin problema aparente . La razón es que los errores de violación de acceso se producen a nivel de sistema operativo (con apoyo del procesador) y obviamente éste no sabe si estás usando bien o no los punteros; sólo le interesa si tu programa en general tiene permisos de acceso o no. Aún así es un error, y para muestra tu programa.

Si no te falla después del gets, significa que en tu PC, cadenados, por casualidad, está apuntando a memoria reservada por tu programa.
Dices que el error se da si descomentas esta línea:

//printf("pp->cadenatres %s\n", pp->cadenatres);

Pero ahí sólo accedes a cadenatres. Mi suposición es que la dirección a la que se inicializa por azar cadenados es la dirección del miembro cadenatres. Así, cuando gets modifica los datos apuntados por cadenados, accidentalmente estás sobrescribiendo cadenatres, que ahora está apuntando a una dirección impredecible. En este caso, probablemente a memoria que no te pertenece, y por eso el printf falla, por intentar acceder a memoria no válida.

Siguiendo con este supuesto, esto no tendría por qué fallar:

if(strcmp(cadenauno,cadenados)==0){

Ya que cadenados apunta a memoria "válida" (desde el punto de vista del sistema operativo).

No sé si también te funcionaba esta línea:

if(strcmp(cadenauno,pp->cadenatres)==0){

pero de ser así, probablemente significa que cadenatres quedó apuntando a una dirección que está cerca del final de una región reservada por tu programa. Me explico: una cadena de C termina cuando se encuentra el caracter de fin de cadena '\0'. Así, la variable cadenauno apunta a la cadena "hola\0". Imagina que la dirección a donde quedó apuntando cadenatres contiene esto: "#5&bfdfgfgfhgh6(\0". Sería posible que los primeros, digamos 4 caracteres, apunten a memoria que anteriormente reservó el programa, y a partir del 5o caracter, entramos a memoria no válida. Eso significaría que, mientras no intentes acceder más allá de su 4o caracter, no habrá error. La función printf, naturalmente, intentará imprimir la cadena entera, y por lo tanto, fallará. El caso de strcmp es distinto, ya que desde el primer caracter detectará que las cadenas son distintas, y por lo tanto deja de procesarlas, y te devuelve el resultado, sin haber llegado nunca a intentar leer el 5o caracter, y por eso no falla.

Todo esto es sólo suposición mía, pero es una posibilidad válida, y más de una vez me he encontrado con casos así. Como ves, un error en un puntero terminaría dejando a otra variable (cadenatres) en un estado inválido, y esta última sería la que provocara fallos. Y no habría nada en el código que indique que cadenados modificó a cadenatres, por lo que, si se tratara de un programa más grande, podría ser muy difícil dar con el fallo.

La moraleja: ten mucho cuidado al manejar punteros y nunca los uses sin inicializar. Eso te evitará muchos dolores de cabeza.