Pregunta

He estado escribiendo C solo unas pocas semanas y no me he tomado el tiempo de preocuparme demasiado por malloc () . Sin embargo, recientemente, un programa mío devolvió una serie de caras felices en lugar de los valores verdadero / falso que esperaba.

Si creo una estructura como esta:

typedef struct Cell {
  struct Cell* subcells;
} 

y luego lo inicializa así

Cell makeCell(int dim) {
  Cell newCell;

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);
  }

  return newCell; //ha ha ha, this is here in my program don't worry!
}

¿Voy a terminar accediendo a caras felices almacenadas en alguna parte de la memoria, o quizás escribiendo sobre celdas previamente existentes, o qué? Mi pregunta es, ¿cómo asigna C la memoria cuando en realidad no he malloc () editado la cantidad adecuada de memoria? ¿Cuál es el valor predeterminado?

¿Fue útil?

Solución

No hay un valor predeterminado para su puntero. Su puntero apuntará a lo que almacena actualmente. Como no lo ha inicializado, la línea

newCell.subcells[i] = ...

Efectivamente accede a alguna parte incierta de la memoria. Recuerde que las subcélulas [i] son ??equivalentes a

*(newCell.subcells + i)

Si el lado izquierdo contiene algo de basura, terminará agregando i a un valor de basura y accederá a la memoria en esa ubicación incierta. Como ha dicho correctamente, deberá inicializar el puntero para que apunte a un área de memoria válida:

newCell.subcells = malloc(bytecount)

Después de qué línea puede acceder a esa cantidad de bytes. Con respecto a otras fuentes de memoria, hay diferentes tipos de almacenamiento que todos tienen sus usos. El tipo que obtenga depende del tipo de objeto que tenga y de la clase de almacenamiento que le indique al compilador que use.

  • malloc devuelve un puntero a un objeto sin tipo. Puede hacer que un puntero apunte a esa región de la memoria, y el tipo de objeto se convertirá efectivamente en el tipo del tipo de objeto señalado. La memoria no se inicializa a ningún valor y el acceso suele ser más lento. Los objetos así obtenidos se denominan objetos asignados .
  • Puede colocar objetos globalmente. Su memoria se inicializará a cero. Para los puntos, obtendrás punteros NULL, para las carrozas también obtendrás un cero apropiado. Puede confiar en un valor inicial adecuado.
  • Si tiene variables locales pero usa el especificador de clase de almacenamiento static , tendrá la misma regla de valor inicial que para los objetos globales. La memoria generalmente se asigna de la misma manera que los objetos globales, pero de ninguna manera es una necesidad.
  • Si tiene variables locales sin ningún especificador de clase de almacenamiento o con auto , su variable se asignará en la pila (aunque C no lo defina así, esto es lo que los compiladores hacen prácticamente, por supuesto) ) Puede tomar su dirección, en cuyo caso el compilador tendrá que omitir optimizaciones como ponerlo en los registros, por supuesto.
  • Las variables locales utilizadas con el especificador de clase de almacenamiento register , se marcan como teniendo un almacenamiento especial. Como resultado, ya no puede tomar su dirección. En los compiladores recientes, normalmente ya no es necesario usar register , debido a sus sofisticados optimizadores. Si eres realmente experto, entonces puedes obtener algo de rendimiento si lo usas.

Los objetos tienen duraciones de almacenamiento asociadas que se pueden usar para mostrar las diferentes reglas de inicialización (formalmente, solo definen cuánto tiempo viven al menos los objetos). Los objetos declarados con auto y register tienen una duración de almacenamiento automático y no se inicializan. Debe inicializarlos explícitamente si desea que contengan algún valor. Si no lo hace, contendrán lo que el compilador dejó en la pila antes de comenzar su vida útil. Los objetos asignados por malloc (u otra función de esa familia, como calloc ) tienen una duración de almacenamiento asignada. Su almacenamiento está no inicializado tampoco. Una excepción es cuando se usa calloc , en cuyo caso la memoria se inicializa a cero ("real" cero. Es decir, todos los bytes 0x00, sin tener en cuenta ninguna representación de puntero NULL). Los objetos que se declaran con static y las variables globales tienen una duración de almacenamiento estático. Su almacenamiento se inicializa a cero apropiado para su tipo respectivo. Tenga en cuenta que un objeto no debe tener un tipo, pero la única forma de obtener un objeto sin tipo es mediante el almacenamiento asignado. (Un objeto en C es una "región de almacenamiento").

Entonces, ¿qué es qué? Aquí está el código fijo. Debido a que una vez que asignó un bloque de memoria, ya no puede recuperar cuántos elementos asignó, lo mejor es almacenar siempre ese conteo en algún lugar. He introducido una variable dim en la estructura que almacena el recuento.

Cell makeCell(int dim) {
  /* automatic storage duration => need to init manually */
  Cell newCell;

  /* note that in case dim is zero, we can either get NULL or a 
   * unique non-null value back from malloc. This depends on the
   * implementation. */
  newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
  newCell.dim = dim;

  /* the following can be used as a check for an out-of-memory 
   * situation:
   * if(newCell.subcells == NULL && dim > 0) ... */
  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim - 1);
  }

  return newCell;
}

Ahora, las cosas se ven así para dim = 2:

Cell { 
  subcells => { 
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    },
    Cell { 
      subcells => { 
        Cell { subcells => {}, dim = 0 }
      }, 
      dim = 1
    }
  },
  dim = 2
}

Tenga en cuenta que en C, el valor de retorno de una función no es necesario para ser un objeto. No se requiere ningún almacenamiento para existir. En consecuencia, no está permitido cambiarlo. Por ejemplo, lo siguiente no es posible:

makeCells(0).dim++

Necesitará una " función libre " que libre es la memoria asignada de nuevo. Porque el almacenamiento para los objetos asignados no se libera automáticamente. Debe llamar a free para liberar esa memoria por cada puntero subcélulas en su árbol. Se deja como un ejercicio para escribir eso :)

Otros consejos

Respuesta corta: no está asignado para usted.

Respuesta un poco más larga: el puntero subcélulas no está inicializado y puede apuntar a cualquier lugar . Esto es un error, y usted nunca debe permitir que suceda.

Respuesta más larga aún: Las variables automáticas se asignan en la pila, las variables globales son asignadas por el compilador y a menudo ocupan un segmento especial o pueden estar en el montón. Las variables globales se inicializan a cero por defecto. Las variables automáticas no tienen un valor predeterminado (simplemente obtienen el valor que se encuentra en la memoria) y el programador es responsable de asegurarse de que tengan buenos valores iniciales (aunque muchos compiladores intentarán darte una pista cuando lo olvides).

La variable newCell en su función es automática y no se inicializa. Deberías arreglar eso pronto. Otorgue a newCell.subcells un valor significativo de inmediato, o apúntelo a NULL hasta que le asigne un espacio. De esa forma, lanzará una violación de segmentación si intenta desreferenciarla antes de asignarle algo de memoria.

Peor aún, está devolviendo una Cell por valor, pero asignándola a una Cell * cuando intenta llenar la matriz subcélulas . Devuelva un puntero a un objeto asignado de montón o asigne el valor a un objeto asignado localmente.

Una expresión habitual para esto tendría la forma de algo como

Cell* makeCell(dim){
  Cell *newCell = malloc(sizeof(Cell));
  // error checking here
  newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
  // more error checking
  for (int i=0; i<dim; ++i){
    newCell->subCells[i] = makeCell(dim-1);
    // what error checking do you need here? 
    // depends on your other error checking...
  }
  return newCell;
}

aunque te he dejado algunos problemas para resolver ...

Y tenga en cuenta que debe realizar un seguimiento de todos los bits de memoria que eventualmente necesitarán ser desasignados ...

Cualquier cosa que no esté asignada en el montón (a través de malloc y llamadas similares) se asigna en la pila, en su lugar. Debido a eso, cualquier cosa creada en una función particular sin ser malloc 'd se destruirá cuando la función finalice. Eso incluye los objetos devueltos; cuando la pila se desenrolla después de una llamada de función, el objeto devuelto se copia en el espacio reservado para la pila por la función de llamada.

Advertencia: si desea devolver un objeto que tiene punteros a otros objetos, asegúrese de que los objetos señalados estén creados en el montón y, mejor aún, cree ese objeto en el montón, a menos que no esté destinado a sobrevivir a la función en la que se crea.

  

Mi pregunta es, ¿cómo asigna C la memoria cuando en realidad no he malloc () editado la cantidad adecuada de memoria? ¿Cuál es el valor predeterminado?

Para no asignar memoria. Tienes que crearlo explícitamente en la pila o dinámicamente.

En su ejemplo, las subcélulas apuntan a una ubicación indefinida , que es un error. Su función debería devolver un puntero a una estructura de celda en algún momento.

  

¿Voy a terminar accediendo a caras felices almacenadas en la memoria en alguna parte, o tal vez escribiendo sobre celdas previamente existentes, o qué?

Tienes suerte de tener una cara feliz. En uno de esos días desafortunados, podría haber limpiado tu sistema;)

  

Mi pregunta es, ¿cómo asigna C la memoria cuando en realidad no he malloc () editado la cantidad adecuada de memoria?

No lo hace. Sin embargo, lo que sucede es que cuando define su celda NewCell, el puntero de subCells se inicializa en el valor basura. Que puede ser un 0 (en cuyo caso obtendría un bloqueo) o algún número entero lo suficientemente grande como para que parezca una dirección de memoria real. El compilador, en tales casos, buscaría cualquier valor que resida allí y se lo devolvería.

  

¿Cuál es el valor predeterminado?

Este es el comportamiento si no inicializa sus variables. Y su función makeCell parece un poco subdesarrollada.

En realidad, hay tres secciones donde se pueden asignar cosas: datos, pila y amp; montón.

En el caso que mencione, se asignaría en la pila. El problema con la asignación de algo en la pila es que solo es válido durante la duración de la función. Una vez que su función regresa, se recupera esa memoria. Entonces, si devuelve un puntero a algo asignado en la pila, ese puntero no será válido. Sin embargo, si devuelve el objeto real (no un puntero), se realizará automáticamente una copia del objeto para que la función de llamada lo use.

Si lo hubiera declarado como una variable global (por ejemplo, en un archivo de encabezado o fuera de una función), se asignaría en la sección de datos de la memoria. La memoria en esta sección se asigna automáticamente cuando se inicia el programa y se desasigna automáticamente cuando finaliza.

Si asigna algo en el montón usando malloc (), esa memoria es buena durante el tiempo que quiera usarla, hasta que llame a free (), momento en el cual se libera. Esto le brinda la flexibilidad de asignar y desasignar memoria según lo necesite (en lugar de usar globales donde todo se asigna por adelantado y solo se libera cuando su programa finaliza).

Las variables locales se asignan " en la pila La pila es una cantidad de memoria preasignada para contener esas variables locales. Las variables dejan de ser válidas cuando sale la función y se sobrescribirán con lo que ocurra a continuación.

En su caso, el código no está haciendo nada ya que no devuelve su resultado. Además, un puntero a un objeto en la pila también dejará de ser válido cuando salga el alcance, por lo que supongo que en su caso preciso (parece que está haciendo una lista vinculada), deberá usar malloc ().

Voy a fingir que soy la computadora aquí, leyendo este código ...

typedef struct Cell {
  struct Cell* subcells;
}

Esto me dice:

  • Tenemos un tipo de estructura llamada Cell
  • Contiene un puntero llamado subcélulas
  • El puntero debe estar en algo de tipo struct Cell

No me dice si el puntero va a una celda o a una matriz de celdas. Cuando se crea una nueva celda, el valor de ese puntero no está definido hasta que se le asigna un valor. Es una mala noticia usar punteros antes de definirlos.

Cell makeCell(int dim) {
  Cell newCell;

Nueva estructura de celda, con un puntero de subcélulas indefinido. Todo lo que hace es reservar una pequeña porción de memoria para que se llame newCell, que es del tamaño de una estructura Cell. No cambia los valores que estaban en esa memoria, podrían ser cualquier cosa.

  for(int i = 0; i < dim; i++) {
    newCell.subcells[i] = makeCell(dim -1);

Para obtener newCell.subcells [i], se realiza un cálculo para compensar las subcélulas por i, luego se desreferenciada . Específicamente, esto significa que el valor se extrae de esa dirección de memoria. Tomemos, por ejemplo, i == 0 ... Entonces estaríamos desreferenciando el puntero de subcélulas (sin desplazamiento). Como las subcélulas no están definidas, podría ser cualquier cosa. ¡Literalmente cualquier cosa! Entonces, esto pediría un valor de algún lugar completamente al azar en la memoria. No hay garantía de nada con el resultado. Puede imprimir algo, puede bloquearse. Definitivamente no debe hacerse.

  }

  return newCell;
}

Cada vez que trabaje con un puntero, es importante asegurarse de que esté establecido en un valor antes de desreferenciarlo. Anime a su compilador a que le dé las advertencias que pueda, muchos compiladores modernos pueden detectar este tipo de cosas. También puede dar valores predeterminados cursis de punteros como 0xdeadbeef (sí, ese es un número en hexadecimal, también es solo una palabra, por lo que parece divertido) para que se destaquen. (La opción% p para printf es útil para mostrar punteros, como una forma cruda de depuración. Los programas de depuración también pueden mostrarlos bastante bien).

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