Hola y bienvenido. Ahora que entendemos la asignación de
memoria dinámica, tenemos que implementarla, tenemos que saber realmente cómo hacerlo en código c.
Así que vamos a hablar de tres funciones
realmente importantes, malloc, calloc, realloc.
Verá que tienen nombres donde la asignación de
memoria calloc y la reasignación.
La forma más simple y común de asignar memoria es a
través de la función de biblioteca estándar llamada malloc.
Esto está asignando memoria dinámica en tiempo de ejecución.
Para llamar a esta función, debe incluir el archivo de encabezado lib estándar.
Usted especifica el número de bytes de memoria que desea asignar como argumento.
Lo que devuelve esta función es la dirección del primer byte de memoria que se asigna.
Debido a que obtiene una dirección devuelta, un puntero es el único lugar para colocarlo.
Entonces, esta es la razón por la que la asignación de memoria dinámica solo
es posible a través de este concepto de puntero porque devuelve direcciones.
Solo puede poner una dirección en un puntero, eso es lo que almacena un puntero, un puntero almacena una dirección.
Así que así es como se hace con una sintaxis muy simple.
Por lo general, cuando crea su puntero, no tiene que hacerlo.
Puede crear su puntero y establecerlo en nulo.
Y luego, en la siguiente línea, podrías malloc.
Pero aquí está la sintaxis estándar que está creando su puntero int, puntero
a un tipo int.
Y luego notarás que llamas malloc.
Y aquí, estamos diciendo malloc, vamos a crear 100 bytes para malloc y
luego lo vamos a convertir en un puntero int.
Debido a que malloc solo devuelve una dirección, debe convertirla
en un puntero final porque esa dirección
almacena un puntero int.
Esta es la forma estándar de hacerlo.
Ahora está solicitando 100 bytes de memoria
y asignándola a la dirección del bloque de memoria al número p.
pNumber apuntará a la primera ubicación int al comienzo de los 100 bytes que se asignaron.
Entonces, lo que esto significa es que, dado que los enteros son 4 bytes por
pieza, puede contener 25 valores int.
Y nuevamente, esto supone que tiene 4 bytes.
Ahora, dependiendo del sistema que esté ejecutando, pueden ser más grandes, tal vez más pequeños.
Entonces deberías ver el problema con esto. Este código no va a
funcionar en diferentes sistemas porque está asignando en base a una suposición, basada
en la suposición del tamaño int.
Por lo tanto, no es realmente bueno usar malloc
de esta manera con un número constante 100.
Lo que quiere hacer es utilizar el operador sizeof.
Por lo tanto, sería mejor eliminar la suposición de que los ints son 4 bytes.
En cambio, si desea asignar espacio para 25 enteros dentro del argumento malloc, especifique
el número de enteros
que desea asignar y luego multiplíquelo por el tamaño de los enteros.
Y esto hará que el código sea mucho más portátil.
Este código ahora funcionará en diferentes sistemas, diferentes sistemas operativos, diferentes arquitecturas.
Aún emite el valor de retorno de la dirección porque tiene que apuntar a un
tipo int, por lo que dice int star. La única diferencia aquí es que usa el operador sizeof.
El operador Sizeof es realmente importante cuando está asignando memoria usando malloc o
usando calloc o realloc.
Siempre usa y desea usar el operador sizeof
para el tipo que está asignando y luego multiplica eso por
cuántos números particulares desea, por lo que si desea almacenar
25 int, entonces haga 25.
Por lo tanto, el argumento de malloc anterior indica claramente que deben estar disponibles
suficientes bytes para acomodar 25 valores de tipo int.
Además, como mencioné, fíjate en el elenco. Esto convierte la dirección, devuelta
por la función al puntero de tipo a puntero int e int.
Malloc devuelve un puntero de tipo void, eso es lo que lo hace tan útil.
Devuelve esa dirección pero es un puntero vacío.
Recuerde, hablamos sobre punteros vacíos y por qué los punteros vacíos
son útiles porque ahora puede tener una función que puede tomar diferentes parámetros y puede
devolver un puntero vacío. Ahora estás viendo el
poder de devolver un puntero
vacío, de nuevo, pero para usarlo, ad para asignarle datos, para desreferenciarlo, tienes
que lanzarlo.
Si no quisiéramos lanzar esto, podríamos decir el número p de la estrella vacía.
No lo lanzamos, pero luego tendríamos que lanzarlo una vez
que quisiéramos quitarle la referencia.
Pero es por eso que tenemos que lanzar desde malloc. Devuelve una dirección y desea que apunte a
un tipo int y devuelve un tipo vacío.
Puede solicitar cualquier número de bytes.
Sin embargo, depende de su sistema y de la cantidad de memoria que tenga en su sistema.
Entonces, si intenta crear o solicitar una tonelada de bytes
o hay alguna limitación, podría fallar.
Y cuando falla, devuelve un valor de nulo, malloc devolverá nulo.
Y esto es bueno porque los punteros cuando los punteros apuntan a nulo, significa que no
apuntan a ninguna memoria, por lo que
no puede corromper o cambiar ninguna memoria sin darse cuenta.
Por lo tanto, siempre es una buena idea, después de
hacer un malloc, verificar inmediatamente si hay un valor nulo
antes de usarlo, tal como sucede
cuando asigna datos o simplemente cuando elimina la referencia a un puntero.
Debes asegurarte de que realmente estás apuntando a algo.
Entonces aquí, estamos asignando algo de memoria para el número p de nuestro puntero y
luego decimos si no es el número p.
Recuerde, el nulo es un cero.
Entonces puedes usar un not en él. En realidad, no tiene que compararlo con nulo.
Puede, puede compararlo con nulo, lo hace más legible, tal vez lo hace más explícito.
Pero puede decir simplemente no número p, y eso en realidad es decir no
uno, y nulo es cero. Entonces esto buscará nulo.
Y si es nulo, eso básicamente significa que la
memoria no se pudo asignar. Si no puede asignar memoria en su programa, está en problemas.
Entonces, lo que debería hacer normalmente en este código para tratar una
falla en la asignación de memoria es abortar, llamar
a exit y salir de su programa. Porque si su programa no puede asignar memoria, su programa está bloqueado.
Por lo general, esto es lo que llama un aborto accidental si no puede asignar memoria.
Al menos cuando hace esto, puede mostrar un mensaje para
finalizar el programa. Es mucho mejor
que permitir que el programa continúe cuando usted no es capaz de asignar memoria.
Debido a que luego puede continuar, puede intentar usar esa memoria,
será nula, por lo que el programa se bloqueará de todos modos,
pero no necesariamente sabrá por qué.
Tienes que mirar la pila de llamadas. Tardará mucho más en depurar ese error.
Liberando memoria. Ahora mencioné que, dado que
usted es el programador responsable de asignar la memoria, debe deshacerse de
la memoria cuando ya no la esté usando porque la memoria es un recurso
vital, especialmente en los sistemas integrados.
No debería utilizar la memoria si no la necesita. Entonces tienes que liberar la memoria.
Siempre desea liberar la memoria cuando ya no sea necesaria.
La memoria que asigne en el montón se liberará automáticamente cuando exista su programa, lo cual
es bueno porque hace algo de limpieza.
Pero es mucho mejor liberar explícitamente la memoria cuando haya terminado,
incluso si es justo antes de salir.
Es más
limpio, siempre hágalo gratis o siempre libere la memoria después
de asignarlo, desea seguir esta regla. Esta es una regla estricta y estricta.
Siempre libere la memoria si la asigna.
Se produce una pérdida de memoria cuando asigna algo de memoria dinámicamente
y no retiene la referencia, por lo que no puede liberarla.
Tal vez esté fuera del alcance de esa referencia que no puede. No lo sueltas.
Eso se llama pérdida de memoria. Hay memoria asignada pero ya no se usa.
Esto ocurre a menudo cuando estás en un bucle y estás asignando memoria en un bucle.
Estás en el bucle. Asignas esta memoria. Lo usas dentro del
bucle, y luego saltas del bucle y ya no lo sueltas.
Cada iteración de bucle lo está asignando.
Entonces, para liberar memoria que ha asignado dinámicamente, aún
debe tener acceso a esa dirección.
Por lo general, desea una memoria libre cuando haya terminado de usarla y, por
lo general, en el método que la asignó.
Sin embargo, si está pasando esa memoria a otra función y luego es
responsabilidad de esa función, pero por lo general desea liberar la memoria y
la función que la asigna.
Para liberar la memoria de un bloque de memoria asignada dinámicamente
cuya dirección ha almacenado en un puntero, todo lo que tiene que hacer
es llamar a la función libre.
Esto también está en la biblioteca estándar. Simplemente pasa el puntero a la función gratuita.
Y, por lo general, después de pasar ese puntero, desea establecerlo en nulo
porque eso significa que no tiene memoria asignada para él.
La función libre tiene un parámetro formal de tipo void
star, por lo que puede pasar cualquier tipo
de puntero y funcionará.
No tienes que lanzarlo ni nada por el estilo.
Siempre que p número contenga la dirección que se devolvió cuando se asignó la memoria, todo el
bloque de memoria estará libre para su uso posterior. Otros programas pueden usar esa memoria, su programa
ahora puede usar esa memoria, lo
que es significativo cuando está en un sistema integrado.
Y luego, como mencioné, siempre desea establecer el puntero en
nulo para significar que ese puntero ya no apunta a nada.
Ese recuerdo se ha liberado.
Hay otra buena función llamada calloc.
Esto tiene un par de ventajas sobre malloc. Es muy similar a malloc,
pero lo que hace es asignar la memoria como un número de elementos en un
tamaño dado y también inicializa la memoria.
Esa es la diferencia, inicialización. Hablé sobre la importancia de la inicialización.
Es por eso
que siempre desea inicializar los datos para que comprenda que hay
datos allí que está esperando.
Cuando anula la inicialización de los datos, podría haber datos allí de los que no tiene ni idea.
Así que siempre inicialice sus datos con algo.
Y calloc es un poco mejor porque lo hace por ti.
Toma dos argumentos, tiene dos parámetros.
El número de elementos de datos para los que se requiere espacio, el tamaño de cada elemento de datos.
Está declarado en el encabezado lib estándar.
Lo llamas como llamas malloc. Se ve más o menos de la misma manera.
En este ejemplo, estamos creando un puntero
de entero y estamos asignando 75 enteros de tamaño
de int. Entonces se necesitan dos parámetros.
Es un poco más limpio porque no tienes que hacer una multiplicación.
En malloc, tenías que hacer 75 eventos de tamaño de tiempo.
Aquí solo dice 75, está creando
75 pulgadas y el tamaño para cada uno es de 4 bytes o
lo que sea que el sistema devuelva del tamaño del operador.
Pero nuevamente, la importancia de esto es que también inicializará todos esos datos, el valor de
retorno podría ser nulo si no puede asignar esa memoria, lo que a su vez no debe abortar.
Pero es muy similar a usar malloc, excepto que la gran ventaja es esta inicialización.
Todos los números enteros se inicializan a cero, y eso es un gran problema,
una gran ventaja.
También es un poco más fácil de usar con los dos parámetros.
Hablemos de otra función llamada realloc.
La función de reasignación le permite reutilizar o ampliar la memoria que
asignó previamente mediante malloc o calloc.
Entonces, esta es una forma de cambiar dinámicamente la cantidad de memoria asignada
mientras está en ejecución.
Entonces, inicialmente podrías crear tanta memoria y luego te das cuenta de
que eso no es suficiente memoria. Bueno, lo que puede hacer allí es simplemente llamar
a realloc, y esto reasignará la memoria.
Espera dos valores de argumento, un puntero que contiene una dirección que
fue previamente convertida por una llamada a malloc.
Entonces, tiene un puntero que tiene algo de memoria asignada o calloc y luego el
tamaño y los bytes de la nueva memoria que desea asignar.
Entonces esto asignará la cantidad de memoria que especifique por el segundo
argumento, ese es el segundo argumento. Transfiere el contenido de la memoria
previamente asignada, referenciada por el puntero, por lo que automáticamente sabe cuánta
memoria había asignado previamente porque pasa el puntero.
Y luego se agregará a eso, la memoria recién asignada.
Y devuelve un puntero vacío a la nueva memoria.
Por lo tanto, debe modificarlo cuando llame a realloc al igual que los demás.
Devuelve nulo si no pudo reasignar, lo que nuevamente
es probablemente un error fatal y debe abortar.
La característica más importante de esta operación es que
realloc conserva el contenido del área de memoria original, solo está ampliando esa memoria.
Puede estar fragmentado porque es posible que no haya podido hacerlo en secuencia, pero
aún tiene toda esa memoria que puede asignar a los datos de ahora.
Veamos un ejemplo.
En este ejemplo, usaremos malloc y luego reasignaremos la memoria y
luego la liberaremos.
Entonces aquí estamos creando un puntero de carácter.
No lo estamos inicializando a nulo, probablemente deberíamos estarlo.
Pero no tiene ninguna memoria asignada para lo que apunta.
Tiene memoria asignada para almacenar el
puntero, que es solo una dirección. Pero si intenta ir a esa dirección, su programa será el anfitrión.
Entonces, lo que haces es llamar malloc.
Aquí estamos diciendo malloc 15.
Esto creará 15 bytes para un puntero de carácter.
Lo lanzamos, porque devuelve un puntero vacío. Entonces, str es un
puntero ahora que tiene memoria asignada para lo que apunta.
Luego podemos usar str copy para pasar algunos datos a lo que apunta,
una cadena e imprimimos
la cadena en la dirección.
Bueno, entonces nos damos cuenta de que lo que asignamos 15 bytes no
es lo suficientemente grande.
Entonces llamamos a realloc, y pasamos el
puntero que es str y queremos reasignar 25 bytes más y luego eso devuelve un
punto vacío ya que tenemos que lanzarlo nuevamente.
Ahora todavía vamos a tener en la memoria a Jason.
Así que ahora podemos llamar a la cadena concatenada, y concatenamos a la cadena com, por
lo que ahora será Jason. com y luego lo imprimimos.
Y luego, por último, pero no menos importante, probablemente lo
más importante de esta diapositiva, por lo que no tenemos una pérdida de memoria es lo que llamamos gratis.
Siempre llame gratis cuando llame a malloc.
Así que echemos un vistazo a este ejemplo dentro de bloques de código.
Aquí está nuestro ejemplo de código dentro de
nuestro editor de bloques de código.
Y lo primero que debe notar es que estamos
incluyendo el archivo de encabezado lib estándar porque ahí es donde están nuestras
funciones malloc y nuestras funciones realloc, así como nuestra función libre.
También debido a que estamos usando algunas funciones str, concatenación
y copia, estamos incluyendo string. h.
Ahora, en la línea ocho, vamos a crear nuestro puntero de carácter.
No tiene memoria asignada para ello.
Deberíamos inicializar esto en nulo porque
todavía no apunta a nada. En la línea 11, llamamos malloc.
Y probablemente deberíamos decir 15 veces el tamaño de
char en caso de que el tamaño de
bytes de char sea diferente y luego lo convertimos en
un puntero de
carácter porque malloc devuelve un vacío. Luego lo copiamos.
Si intentáramos copiar esto y no hicimos nada en la línea 11,
el programa fallaría porque no tenemos memoria asignada para lo que apunta la cadena.
Luego imprimimos la cadena y la dirección.
Ahora llamamos realloc.
Necesitamos almacenar más datos que 15 bytes, por lo
que vamos a reasignar con 25 bytes
y probablemente queremos volver a hacer sizeof char para que
sea coherente.
Lanzamos esto y luego lo concatenamos en nuestra ubicación de memoria
existente que contiene a Jason. com, y vamos a imprimir jason. com.
Y luego, por último, pero no menos importante, en la línea 20, liberamos esa memoria
que es realmente importante.
Así que sigamos adelante y ejecutemos esto. Y lo que verá es
que cuando lo imprimimos por segunda vez, tenemos la concatenación.
Entonces, nuestra
primera cadena, tenemos memoria asignada y es una dirección 000C2918.
Luego creamos una nueva dirección reasignando y tomando el tamaño existente del puntero que
le pasamos como argumento.
Así que 000C0CD8 en realidad tiene más datos
asignados, los datos existentes
en jason y 25 bytes más.
Y entonces lo que notan es que la concatenación funciona.
Porque cuando lo reasignamos,
colocamos el contenido de Jason en esa memoria y simplemente lo ampliamos.
Entonces, hay muchas ventajas de usar estos malloc, estos calloc y las llamadas de realloc gratuitas.
Quieres usarlos, quieres entenderlos. Funciones bastante simples de usar.
Entonces, en nuestra última diapositiva, repasemos algunas pautas.
Quiere evitar la asignación de una gran cantidad de pequeñas cantidades de memoria, no es eficiente.
Cuando asigna memoria en
el montón, tiene algunos gastos generales.
Y si asigna pequeños bloques de memoria, tendrá mucha más sobrecarga que asignar
menos bloques más grandes.
Así que asigne la memoria que
necesita, pero no lo haga cada dos declaraciones con reasignación, haga
grandes porciones.
Conserve la memoria solo el tiempo que la necesite. La memoria es un recurso escaso.
Cuando está en el montón, es aún más escaso.
Entonces, cuando haya terminado con un bloque de memoria, llame gratis.
Siempre asegúrese de proporcionar la memoria de liberación que ha asignado, decida en qué parte
de su código liberará la memoria cuando escriba el código que la asigna.
Asegúrese de tener acceso a ese puntero.
Si necesita hacer que el puntero sea global, entonces debe hacerlo.
Pero, por lo general, dondequiera que asigne esa función, debe liberarla en esa función.
Si tiene otras funciones que le liberan
memoria, entonces está asumiendo que no es bueno. Hay demasiado acoplamiento y dependencias allí.
La persona que lo crea debe ser la persona que lo elimina.
No ponga la responsabilidad en otras funciones y cosas por el estilo.
Asegúrese de no sobrescribir inadvertidamente la dirección de memoria que ha asignado en
el montón antes de liberarla, esto provocará una pérdida de memoria.
Por lo tanto, si intenta escribir demasiados bytes o si tiene desbordamientos, es posible
que tenga problemas.
Por lo tanto, tenga especial cuidado cuando asigne memoria dentro de un
bucle porque esto puede causar problemas en los desbordamientos del búfer.
Espero que esto haya sido útil. La asignación de memoria dinámica es un gran problema.
Te daré un desafío que te ayudará a familiarizarte con
esto, pero asegúrate de entender este concepto. Gracias.