Stack Overflow en español Asked by Nathra1967 on January 4, 2022
Hoy estaba adelantando uno de mis primeros programas en C, y en medio de tal labor, utilicé un array bidimensional. Esta vez, tras meditar acerca de la sintaxis que muchas veces se aprende sin realmente comprender la lógica de ejecución, en este caso de la asignación de memoria en tiempo de ejecución. Me encuentro con una idea que parece algo confusa y que comparto con la intención de comprobar si lo que pienso es errado y de este modo puede haber alguna corrección que me de "luces" sobre este asunto.
Entiendo en lo que he aprendido, que un puntero es una variable que contiene una dirección de memoria y permite acceder a ella. Dicha dirección, puede contener valores constantes u otras variables. Llegado a este punto, un puntero puede apuntar a otro puntero porque este también es una variable, cuya variabilidad está determinada por la posición de memoria a la que apunta.
Ahora bien, de aquí en adelante me siento menos seguro de lo que digo y es sobre lo que tengo dudas.
Cuando un puntero apunta a un array, realmente lo hace al primer valor, de lo cual se supone que los sucesivos elementos del array, al estar almacenados en posiciones contiguas, son modificables al acceder con
puntero[n]
teniendo en cuenta quen
es la magnitud del desplazamiento desde la variable a la que realmente apunta el puntero.
Sobre que esto que digo me pregunto:
Continuando con las dudas, al asignar memoria dinámica a un puntero.
Por lo que se, un puntero con memoria dinámica es una manera de crear un array y, con la lógica del párrafo anterior, el puntero sigue apuntando a una posición de memoria única que en este caso, es el primer elemento del array, con lo que llego a la conclusión de que lo que realmente pasa con la función realloc()
. Es que se le dice al compilador
"Esta memoria de la zona libre puede usarse"
Pero en realidad no está cambiando la naturaleza del puntero, pues este sigue apuntando al primer elemento y, nos desplazamos a la memoria contigua del mismo modo que se decía antes, con la diferencia de que sin asignar la memoria esto no sería posible porque no está disponible para usarse.
Con todo, llego a lo que me hizo pensar en todo esto. Podría decirse que al declarar un array bidimensional, si lo vemos como una matriz, el primer array, de la misma naturaleza del descrito antes, que determina las "filas" de la matriz, contiene a su vez punteros que apuntan al primer elemento de otros arrays. Con lo cual pienso que la memoria asignada a un array bidimensional solo es contigua en dos casos:
Por lo tanto, todo el array bidimensional no sería contiguo en el heap, sino que habría un montón de fragmentos contiguos distribuidos aleatoriamente en el heap relacionados entre si gracias a los punteros. Me resulta muy fascinante y curioso este detalle, para terminar debo preguntar:
Seria interesante saber qu pueden corregirme o añadir algo a lo que digo.
¿Cómo funcionan realmente los arrays bidimensionales dinámicos en C?
Cuando creamos un array bidimensional por medio de memoria dinámica, lo único que necesitamos es un array de punteros:
const int N = 4;
int** p = calloc(N, sizeof(int*));
Este código lo único que hace es reservar memoria para un array de 4 elementos e inicializa la memoria en 0
. Cada elemento es un puntero y su tamaño en bytes dependerá si la máquina es de 32 ó 64 bits.
En una máquina de 32 bits la expresión sizeof(int*)
daría como resultado 4
bytes (ese sería el tamaño del puntero) y en una de 64 bits el tamaño sería de 8
bytes.
Visualmente lo veríamos así:
Aquí queda claro que el puntero p
queda apuntando hacia la dirección 0x016
(es la dirección base del array).
Luego debemos lograr que cada elemento del array de punteros, apunten hacia la dirección base de un array, que puede ser un array de int
, de char
, etc.
En este caso debería ser un array de enteros:
const int N = 4;
const int M = 3;
int** p = calloc(N, sizeof(int*));
for(int i = 0; i != N; ++i)
{
p[i] = malloc(M * sizeof(int));
for(int j = 0; j != M; ++j)
p[i][j] = rand() % 10;
}
Este código hace 2 cosas:
1.- Crea un array de int
de forma dinámica y le asigna la dirección base en X
posición del array de punteros.
2.- Rellena de valores cada posición del array al que apunte cada puntero.
En la imagen se puede ver claro que cada puntero apunta hacia el primer elemento de un array.
En estas dos imágenes podemos observar dos cosas importantes:
1.- El array de punteros es contiguo (sus elementos están juntos entre sí).
2.- Cada puntero apunta a un array contiguo. Sin embargo, cada array de enteros no son contiguos entre sí, por lo tanto, habrá huecos alrededor.
El heap lo podríamos visualizar de este modo (dale clic a la imagen para verla mejor):
En la imagen podemos observar tres cosas:
1.- Cada array en realidad son como especie de bloque de memoria.
2.- Los elementos de cada bloque son contiguos.
3.- Los bloques de memoria (o los arrays) no son contiguos entre sí, hay huecos alrededor.
Entendiendo como el array bidimensional se ve reflejado en memoria, podríamos responder a estas preguntas:
¿Lo que digo nuevamente, es acertado?
Estás en todo lo correcto. Las filas es como si fuera el array de punteros y cada array al que apunte el puntero es como si fuera las columnas y así es como se crea una especie de "matriz".
Y sí, por medio de los punteros es como se puede acceder a cada uno de esos arrays. Básicamente ya tenías la respuesta.
¿Es esto correcto?
Sí, estás en lo correcto. Un puntero simplemente contendrá la dirección de memoria del primer elemento del array. Con esta dirección guardada se le suma un offset para poder llegar a la dirección de memoria que se quiera leer o escribir.
¿Todos los arrays terminan con o es algo único de los arrays de caracteres?
No. En C, solo los arrays de caracteres necesitan terminar con el caracter nulo y esto se debe porque para poder recorrer todo el array se necesita tener un inicio y un fin (el caracter nulo nos indicará el fin).
Continuando con las dudas, al asignar memoria dinámica a un puntero. ¿Que pasa en realidad?
Con este ejemplo se refleja el funcionamiento:
int* p = malloc(5* sizeof(int));
Aquí pasa dos cosas:
1.- La función malloc
hace una llamada al sistema operativo para que le asigne al proceso actual (es decir, al programa que se está ejecutando) un bloque de 24 bytes (5 * sizeof(int)
).
2.- Cuando el sistema asigne la memoria, la función malloc
retornará la dirección base de dicho bloque y este modo, por medio del puntero p
se podrá acceder a cualquier posición del bloque reservado.
¿Nuevamente, es esto correcto?
No. El compilador no se encarga de asignar memoria dinámica, sino, funciones como malloc
, calloc
o realloc
.
Si no hay memoria disponible, estas funciones retornarán NULL
.
Answered by MrDave1999 on January 4, 2022
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP