Solución:
Lo que se enseñó es que
malloc(10*sizeof(char))
asigna suficientes bytes en el montón para almacenar 10 caracteres y devuelve un puntero al primer byte que se puede guardar en otra variable de la siguiente manerachar *x
. Para liberar la memoria que uno usaría
= malloc(10*sizeof(char))free(x)
.
“En el montón” es un concepto de implementación, no un concepto de lenguaje C. El lenguaje C en sí no se ocupa de dividir la memoria en áreas separadas con características diferentes y, de hecho, no es necesariamente cierto que cualquier implementación de C dada lo haga.
Incluso en un curso introductorio, tal vez especialmente en un curso introductorio: es mejor utilizar conceptos del lenguaje C que conceptos nativos de un determinado estilo de implementación. El concepto de lenguaje C relevante en este caso es duración de almacenamiento:
Un objeto tiene una duración de almacenamiento que determina su vida útil. Hay cuatro duraciones de almacenamiento: estático, subproceso, automático y asignado.
(C2011, 6.2.4 / 1)
El objeto asignado por su malloc()
llamar, (en) a lo que su puntero x
puntos, tiene una duración “asignada”. Eso significa que su vida dura hasta que ese objeto es liberado por una llamada a free()
. Note bien la distinción aquí entre variable x
, un puntero con duración de almacenamiento automático, y el objeto al que x
puntos inicialmente, un objeto sin tipo del tamaño de 10 char
s.
Hay (mucho) más, pero este punto de su viaje es temprano para profundizar en el estándar. Sin embargo, encuentro esta caracterización más útil para abordar preguntas como las que usted plantea.
Pero hay otra forma de hacer que una computadora reserve suficiente memoria para almacenar 10 caracteres, es decir
char c[10]
.
Sí, eso es verdad.
- Está en el fragmento de código anterior c también es un puntero de tipo
char*
?
No. Dentro del alcance de esa declaración, el identificador c
se refiere a una matriz de 10 char
s. Existe una estrecha relación entre matrices y punteros, pero no son lo mismo en absoluto. Este es un punto de crucial importancia, y uno con el que tropiezan muchos nuevos programadores de C, así que repito: las matrices y los punteros no son lo mismo. Sin embargo, los detalles darían lugar a una respuesta completamente diferente, y una que ya puede encontrar varias veces aquí en SO.
Para decirlo de otra manera, identificador c
designa un tipo de cosa a la que x
El valor podría apuntar, pero recuerde que x
El valor (puntero) es distinto del objeto al que apunta.
- Lo hace
char c[10]
también reservar memoria en el montón comomalloc
¿lo hace?
Si su declaración de c
aparece dentro de una función y luego declara una matriz con duración de almacenamiento automático. Esto significa que la vida útil de la matriz dura hasta que el identificador c
sale de su alcance. Es la preocupación de la implementación dónde se encuentra el almacenamiento para esa matriz, pero en una implementación que proporciona una distinción entre pila y pila, lo más probable es que el almacenamiento esté en la pila, no en el montón.
- ¿Son equivalentes las formas de asignar memoria?
No. malloc()
asigna un objeto con una duración de almacenamiento asignada, cuya vida útil el programa es responsable de gestionar explícitamente. El otro asigna un objeto con duración de almacenamiento automático, cuya vida está determinada por el alcance del identificador.
char c[3] = "aaa";free(c);
devuelve un error de tiempo de ejecución; entonces parece que no puedo liberar la memoria que he asignado con charc[3]
. ¿Porqué es eso?
Más directamente, es porque las especificaciones para el free()
función decir explícitamente
[I]Si el argumento no coincide con un puntero devuelto anteriormente por una función de administración de memoria, o si el espacio ha sido desasignado por una llamada a free o realloc, el comportamiento no está definido.
(C2011, 7.22.3.3/2)
Es decir, el estándar no requiere un error de tiempo de ejecución (o cualquier otro comportamiento en particular) si intenta liberar un puntero a un objeto con duración automática, pero rechaza explícitamente cualquier promesa de que pueda liberar memoria de esa manera.
Pero una respuesta más satisfactoria, creo, es que free()
es cómo se marca el final de la vida útil de un objeto con una duración de almacenamiento asignada, no uno con duración automática (u otra). Dónde se encuentra el almacenamiento del objeto (p. Ej., Pila vs. montón) es auxiliar.
- ¿En el fragmento de código anterior c también hay un puntero de tipo char *?
No, no es. Es una matriz de diez char
.
Sin embargo, el nombre de una matriz puede, cuando se usa en un contexto donde se espera un puntero, convertirse en un puntero y, por lo tanto, usarse efectivamente como si fuera un puntero.
- ¿Char c[10] también reserva memoria en el montón como lo hace malloc?
No. Heap y stack tampoco son términos completamente precisos, pero no lo extenderé más.
Qué malloc()
lo que hace se llama “asignación de memoria dinámica” de acuerdo con el estándar.
El comportamiento de char c[10];
depende del contexto.
- Si está dentro de un alcance de bloque (dentro de un par de
{}
) crea una matriz de duración de almacenamiento automático. Esa matriz deja de existir, en lo que respecta a su programa, cuando se sale del alcance (por ejemplo, si la función regresa). - Si está en el alcance del archivo (fuera de una función), crea una matriz de duración de almacenamiento estático. La matriz se creará una vez y seguirá existiendo hasta la finalización del programa.
- ¿Son equivalentes las formas de asignar memoria?
No.
char c[3] = “aaa”; libre (c); devuelve un error de tiempo de ejecución; entonces parece que no puedo liberar la memoria que he asignado con el carácter c[3]. ¿Porqué es eso?
Porque free()
solo tiene un comportamiento definido cuando se pasa un puntero a la memoria asignada dinámicamente, es decir, devuelto por malloc()
, calloc()
, o realloc()
o un NULL
puntero (que causa free()
hacer nada).
c
es una matriz de duración de almacenamiento estática o automática, según el contexto, como mencioné anteriormente. No se asigna dinámicamente, por lo que pasarlo a free()
da un comportamiento indefinido. Un síntoma común de eso es un error en tiempo de ejecución, pero no es el único síntoma posible.
Consideraciones sintácticas:
En primer lugar, los tipos de c
y x
son diferentes: el tipo de x
es lo que esperas char*
, mientras que el tipo de c
es char[10]
, que es una matriz de diez elementos de carácter.
Por lo tanto, x
y c
no puede ser completamente equivalente: cuando dices x
, el compilador simplemente piensa en una sola dirección de un solo char
. Sin embargo, cuando dices c
el compilador piensa en todo el objeto de matriz con sus diez char
elementos. En consecuencia, el código
printf("sizeof(x) = %zdn", sizeof(x));
printf("sizeof(*x) = %zdn", sizeof(*x));
printf("sizeof(c) = %zdn", sizeof(c));
imprimirá
sizeof(x) = 8
sizeof(*x) = 1
sizeof(c) = 10
en una máquina de 64 bits. sizeof(x)
da la cantidad de bytes necesarios para almacenar una dirección, sizeof(*x)
da la cantidad de bytes que el puntero x
apunta a, y sizeof(c)
da la cantidad de bytes necesarios para almacenar una matriz completa de diez char
elementos.
Entonces, ¿por qué puedo usar c
prácticamente en cualquier lugar donde pueda usar x
¿C ª?
El truco se llama decaimiento del puntero de matriz: Siempre que use una matriz en un contexto donde se espera un puntero, el compilador descompone silenciosamente la matriz en un puntero a su primer elemento. Solo hay dos lugares en C, donde realmente puede usar una matriz. El primero es sizeof()
(que es la razón por la que sizeof(x) != sizeof(c)
), y el segundo es el operador de dirección &
. En todos los demás casos, cualquier uso de c
invoca la caída del puntero de matriz. Esto incluye cosas como c[3]
. Esta expresión se define como equivalente a *(c+3)
, por lo que el compilador descompone la matriz c
en un puntero a su primer elemento, luego aplica aritmética de puntero c+3
y luego elimina la referencia del puntero resultante. Suena complicado, es alucinante, pero tiene el efecto deseado de acceder al cuarto elemento de la matriz.
De todos modos, con las consideraciones sintácticas fuera del camino, veamos la asignación de memoria real:
-
malloc()
reserva un bloque de memoria del tamaño dado, y ese bloque sigue siendo válido hasta que llamefree()
en el puntero quemalloc()
regresó.Esto es independiente del flujo de control en su programa.: Una función puede devolver el resultado de
malloc()
a su interlocutor y deje que el interlocutor lo libere. O puede pasar el resultado demalloc()
a alguna otra función que lo libere. O puede devolver el resultado a su interlocutor y el interlocutor lo pasa a otra función para liberarlo. O el resultado puede almacenarse en algún otro objeto de memoria durante algún tiempo. Y así sucesivamente y así sucesivamente. Las posibilidades son tan variadas como el código fuente que se está escribiendo en todo el mundo.Cabe destacar que es un gran error utilizar un puntero después de liberarlo. Si haces eso, es posible que aparezcan elefantes rosados y te pisoteen hasta la muerte, en lo que respecta al estándar C. Es su trabajo como programador asegurarse de que cada puntero mal ubicado se libere exactamente una vez. Si no lo hace, bueno, todas las apuestas están canceladas.
-
Si usted dice
char c[10];
en el alcance del archivo (fuera de las funciones o
struct
definiciones), está declarando un variable global. Esta matriz existirá desde antesmain()
es llamado justo hasta la muerte de su proceso (ya sea al regresar demain()
, o ejecutandoexit()
). -
Si usted dice
char c[10];
dentro de una función, estás declarando un variable local. Esta matriz surge cuando se ejecuta su declaración y deja de existir al final del bloque adjunto (la parte entre un par de llaves
{}
).Por lo tanto, la asignación y la desasignación están estrictamente ligadas al flujo de control de su programa.
-
Si usted dice
char c[10];
dentro de la definición de un
struct
, estás declarando un variable miembro. Esta matriz existirá mientras el objeto circundante (elstruct
) existe. Si el objeto circundante es global, la vida útil de las matrices es la de un global, si el objeto circundante es local, la vida útil de las matrices es la de un local, y si el objeto circundante es miembro de algún otro objeto, la vida útil de las matrices es ese del otro objeto (esto es recursivo).