[C con Clase] Dudas sobre punteros que apuntan a punteros

Davidson, Steven srd4121 en njit.edu
Mie Ene 23 05:26:48 CET 2013


Hola Javier,

2013/1/22 Javier <javiersalvadormarco en gmail.com>

> Buenas noches chicos,
>
> Este es mi primer mensaje en esta maravilla de comunidad que nos ayuda a
> perfeccionar nuestras habilidades en el mundo de la programación, así que
> antes de nada, ¡muchas gracias por todo!
>
>
Bienvenido a nuestra lista de correo-e y a este "mundillo" de la
programación.

Ahora voy al grano y os expongo mi pregunta, que va relacionada con lo que
> más me cuesta de entender en el mundo de la programación: los punteros.
>
>
Eso nos pasa a todos cuando vemos este tema, tanto si es la primera vez
como la quinta, décima, etc.; incluso para los programadores veteranos.

Parece que después de mucho machacar el tema he conseguido entender de
> forma más o menos fiable los punteros que apuntan a funciones u objetos,
> pero cuando hablamos de punteros que apuntan a punteros o arrays la cosa se
> me atraganta. Este es el ejemplo


No debería ser tan diferente de lo que ya conoces. Al fin y al cabo, un
tipo de puntero sigue siendo un tipo de dato; y una dirección de memoria
sigue siendo un valor - en este caso, entero - que podemos guardar en una
variable y realizar ciertas operaciones con ella.

Ahora bien, entiendo que quizá tengas dudas en cuanto al uso que puedas dar
a punteros a punteros.

[CORTE]

Entiendo que lo que os he marcado como 1 es la inicialización de un
> puntero, el f, que va a apuntar a la dirección de memoria de otro puntero,
> el cual todavía falta por crear.
>
>
Bueno, para este tema, es mejor hablar con claridad, por lo que tendremos
que ser muy cuidadosos con la terminología que usemos.

float **f;

es una declaración y definición de la variable 'f'.

En la parte marcada como 2, reservamos 10 posiciones de memoria a las que
> apuntará f, habiendo en cada una de ellas un puntero. Si no estoy
> equivocado, f en este momento podrá recoger el valor de cada uno de los
> punteros que hay, pudiendo moverse a través de sus direcciones de memoria
> para obtener su valor: Ejemplo f[0] recogerá el valor que apunte el puntero
> de la array 0. ¿Es así?
>
>
Creo que lo has dicho correctamente, pero insisto que hay que expresarse
con cuidado, en estos temas. 'f[0]' guarda un valor de tipo 'float *'; es
decir, sirve para guardar una dirección de memoria.

Aquí me gustaría hacer una pregunta sobre el tema, y es por qué no se
> utilizaría para extraer el valor real el parámetro *f[0] si lo que queremos
> es obtener un valor y no una dirección de memoria. Esto me cuesta un poco
> de ver, ya que con los punteros que apuntan a objetos se hace de esa forma
> y con arrays he visto que no.
>
>
Se puede hacer, pero como queremos crear arrays de arrays, sería un tanto
incoherente ver algo así:

*f[0] = 3.12f;
f[0][1] = -.4127f;
f[0][2] = 9901231857.2f;
...

Ciertamente, podríamos usar * para acceder a todos los demás elementos así,

*f[0] = 3.12f;
*(f[0]+1) = -.4127f;
*(f[0]+2) = 9901231857.2f;
...

Sin embargo, es más legible usar [], y además nos ahorramos escribir tres
operaciones: *, (), y +.

De todas formas, a estas alturas del programa, no podríamos - o mejor dicho
no deberíamos - acceder al puntero en 'f[0]', porque no contiene
inicialmente ningún valor válido ni deseado; o sea, contiene "basura".

Pasamos a la parte marcada como 3. Si esto lo entiendo bien, en este
> momento vamos creando en cada una de las 10 aposiciones en las que teníamos
> un puntero, un array de 10 floats. Para acceder a estas posiciones bastará
> con poner por ejemplo: f[3][3], donde el primer corchete desplazaría el
> puntero que hemos creado en el punto 1, y el segundo corchete los que hemos
> creado en el 2. ¿Es correcto?
>
>
Sí y no.

Vamos creando varios arrays de 10 'float'. Guardamos la dirección de
memoria del comienzo de cada array en cada puntero del array que creamos en
(2). Al aplicar 'f[3]', estamos realizando el siguiente cálculo:

dir1 = f + 3*sizeof(float**);

Si 'f' guarda como valor la dirección de memoria de 32 bits (4 bytes) de
0x55AAEE00, entonces tenemos,

dir1 = 0x55AAEE00 + 3 * 4 = 0x55AAEE0C

y obtenemos su valor, que es una dirección de memoria. Digamos que es
0x99FFBB00. Esta dirección de memoria es el comienzo del array que creamos
en la cuarta iteración del bucle 'for' en (3), que vendría a ser esta
sentencia:

f[3] = new float[10];

Al aplicar 'f[3][3]', estamos haciendo un cálculo similar al anterior:

dir2 = f[3] + 3*sizeof(float*);

por lo que obtendremos,

dir2 = 0x99FFBB0C

y el valor guardado en tal dirección de memoria, que podría ser: -8.123f.

Parte 4. Aquí mi duda radica en el porqué hemos de crear un bucle para
> liberar todas las posiciones de memoria, si después realizamos la operación
> 5:  delete[] f. Supongo que esto es un fallo de concepto mío a la hora de
> entender la reserva de memoria, ya que yo me imaginaba que al hacer    f =
> new float *[10] reservamos una cantidad de memoria, y después al hacer el
> bucle   for(n = 0; n < 10; n++) f[n] = new float[10] fragmentábamos parte
> de esa memoria para asignarla a cada una de las nuevas arrays de floats.
> Supongo que he mezclado churros con melindras y que cada vez que se utiliza
> el new se reserva memoria distinta.
>
>
El operador 'new[]' simplemente se comunica con el sistema operativo (S.O.)
para pedir que se le adjudique un bloque contiguo de memoria a este
programa. Por lo tanto, cada vez que realicemos la operación: new[],
estamos pidiendo otro bloque de memoria. Sería problemático si todas las
adjudicaciones se basaren en el mismo bloque de memoria. Recuerda que el
S.O. no sabe nada de las "intenciones" de los programas que se ejecutarán
bajo su control. Si un programa requiere usar un recurso compartido de
memoria para sus fines, entonces es la responsabilidad del programa
gestionarlo. De hecho, C++ sí acepta esta forma de crear nuevos objetos a
partir de memoria existente, usando el operador: 'new()' o incluso
'new[]()', para un array.

Cada 'new[]' provoca una adjudicación nueva de un array, lo cual implica
que tendremos diferentes direcciones de memoria al comienzo de cada array.
En nuestro ejemplo, hemos usado 'new[]' 11 veces, por lo que debemos usar
'delete[]' 11 veces; y por tanto la regla de: un 'delete[]' por cada
'new[]'.

Muchísimas gracias por vuestra ayuda chicos. Soy programador de PL/SQL y el
> mundo orientado a objetos me gusta, pero también me vuelve loco.
>
>
Viendo que eres programador de SQL, realmente este tema de punteros no
debería ser conceptualmente difícil para ti, ya que usas el mismo mecanismo
a la hora de diseñar - y de programar - bases de datos relacionales. Como
ya sabes, al diseñar el esquema de una tabla, lo típico es mantener una
columna a modo de identificador como un número entero. Este mismo
identificador aparecerá en el esquema de otra tabla. A la hora de usar las
tablas, usaremos ese mismo identificador para relacionarlas. Para usar la
información en cada tabla, usamos ese identificador para obtenerla
indirectamente.

He aquí el quid de la cuestión de los punteros: la indirección. Por cada
tipo de puntero, tantos niveles de indirección tendremos. En el ejemplo,
con un doble puntero, necesitamos realizar dos "saltos" de indirección para
llegar al número de tipo 'float'.

Otra forma de ver los punteros o, mejor dicho, las direcciones de memoria
es que la memoria principal (RAM) es como un array gigantesco, y cada
dirección de memoria actúa como un índice, para tal array. Por lo tanto,
cada vez que usemos un puntero doble, triple, etc., realmente lo vamos a
usar como un índice doble, triple, etc.; o sea, un índice a un índice a un
índice, y así sucesivamente.


Espero que todo esto te aclare las dudas.

Steven
------------ próxima parte ------------
Se ha borrado un adjunto en formato HTML...
URL: <http://listas.conclase.net/pipermail/cconclase_listas.conclase.net/attachments/20130122/8e8b466b/attachment.html>


Más información sobre la lista de distribución Cconclase