Bien, vamos a volver un poco con las cadenas. En el código que vamos a ver, todas las asignaciones serán globales, es decir, fuera de cualquier función y también fuera de (main().
Antes hemos visto que:
char mi_cadena[40] = "Ted";
asigna espacio para un array de 40 bytes y coloca la cadena en los primeros 4 bytes (tres para los caracteres entre comillas y un cuarto para la terminación '\0').
En realidad, si sólo queremos guardar el nombre "Ted" podríamos escribir:
char mi_nombre[] = "Ted";
entonces el compilador contaría los caracteres, dejaría espacio para el caracter nul y guardaría los cuatro caracteres en la memoria devolviendo su ubicación en el nombre del array, que en este caso sería mi_nombre.
En algún código, en lugar de lo anterior, podrías ver:
char *mi_nombre = "Ted";
que es una alternativa. ¿Hay alguna diferencia entre ambos? La respuesta es.. si. Utilizando la notación de array se toman 4 bytes de almacenamiento en un bloque estático de memoria, uno para cada carácter y uno para el carácter de terminación nul. En la notación de punteros se requieren esos mismos 4 bytes más N bytes para guardar la variable puntero mi_nombre (donde N depende del sistema, y por lo general tendrá un mínimo de 2 bytes y puede ser de 4 bytes o más).
En la notación de array, mi_nombre es la abreviación de &mi_nombre[0], que es la dirección del primer elemento del array. Como el array tiene una ubicación fija en tiempo de ejecución, el array es una constante (no una variable). En la notación de punteros mi_nombre es una variable. Dependiendo de lo que quieras hacer con el resto del programa, un método será mejor que el otro.
Demos un paso más y veamos qué ocurre si cada una de estas declaraciones se realizan dentro de una función, en lugar de realizarlas globalmente.
void mi_funcion_A(char *ptr) { char a[] = "ABCDE" . . } void mi_funcion_B(char *ptr) { char *cp = "FGHIJ" . . }
En el caso de mi_funcion_A, el dato es el contenido (los valores del array a[]) y este array se inicializa con los valores ABCDE. En el caso de mi_funcion_B, el dato es el valor del puntero cp. El puntero se inicializa para que apunte a la cadena FGHIJ. Tanto en mi_funcion_A como en mi_funcion_B se definen variables locales y por tanto la cadena ABCDE y el valor del puntero cp se guardan en la pila. La cadena FGHIJ puede guardarse en cualquier lugar (en mi sistema se guarda en el segmento de datos).
Por cierto, la inicialización de arrays con variables automáticas como vemos en mi_funcion_A no se permite en el antiguo C de K&R y sólo se permite en la "mayoría de edad" del nuevo ANSI C. Esto puede ser importante en casos donde sea necesaria la portabilidad y la compatibilidad hacia atrás.
Mientras hablamos de las similitudes y las diferencias entre punteros y arrays, vamos a ver los arrays de varias dimensiones. Consideremos por ejemplo el array:
char multi[5][10];
¿Qué significa esto? Bien, veámoslo desde la siguiente perspectiva:
char multi[5][10];
Ignoremos la parte subrayada que es el "nombre" del array y nos queda char y [10] que sería un array de 10 caracteres. El nombre multi[5] en sí es un array indicando que hay 5 elementos donde cada uno es un array de 10 caracteres. Por tanto, tenemos un array de 5 arrays de 10 caracteres cada uno..
Asumamos que rellenamos este array de dos dimensiones con datos de algún tipo. En memoria parecería como si los arrays hubieran sido inicializados por separado mediante algo así:
multi[0] = {'0','1','2','3','4','5','6','7','8','9'} multi[1] = {'a','b','c','d','e','f','g','h','i','j'} multi[2] = {'A','B','C','D','E','F','G','H','I','J'} multi[3] = {'9','8','7','6','5','4','3','2','1','0'} multi[4] = {'J','I','H','G','F','E','D','C','B','A'}
Podríamos tener acceso individual a cada elemento con la siguiente sintáxis:
multi[0][3] = '3' multi[1][7] = 'h' multi[4][0] = 'J'
Como los arrays se escriben en memoria de forma contigua, nuestro bloque de memoria para lo anterior debería ser:
0123456789abcdefghijABCDEFGHIJ9876543210JIHGFEDCBA ^ |_____ comienzo de la dirección de &multi[0][0]
Fíjate que no he escrito multi[0] = "0123456789", porque si lo hubiera hecho, se habría agregado la terminación '\0' a los carácteres que van dentro de las comillas dobles y tendría 11 caracteres por línea en lugar de 10.
Mi objetivo aquí es mostrar cuanta memoria se utiliza para arrays de dos dimensiones, esto es, un array de dos dimensiones de caracteres, NO un array de "cadenas".
Ahora el compilador sabe cuantas columnas tiene el array y puede interpretar a multi + 1 como la dirección de la 'a' ubicada en la segunda línea, esto es, agrega 10 que es el número de columnas para llegar a la dirección de 'a'. Si hubiéramos utilizado enteros en este array en lugar de caracteres, el compilador agregaría 10*sizeof(int) que, en mi máquina sería 20. Por tanto, la dirección del valor 9 ubicado en la cuarta línea sería &multi[3][0] o *(multi + 3) en notación de punteros, y para llegar al contenido del segundo elemento de la cuarta línea agregaríamos 1 a esta dirección y desreferenciaríamos el resultado así:
*(*(multi + 3) + 1)
Pensando un poco vemos que:
*(*(multi + row) + col) y multi[row][col] llegan al mismo resultado.
El siguiente programa muestra esto utilizando arrays de enteros en lugar de arrays de caracteres.
------------------- programa 6.1 ---------------------- /* Programa 6.1 de PTRTUT10.HTM 6/13/97*/ #include <stdio.h> #define ROWS 5 #define COLS 10 int multi[ROWS][COLS]; int main(void) { int row, col; for (row = 0; row < ROWS; row++) { for (col = 0; col < COLS; col++) { multi[row][col] = row*col; } } for (row = 0; row < ROWS; row++) { for (col = 0; col < COLS; col++) { printf("\n%d ",multi[row][col]); printf("%d ",*(*(multi + row) + col)); } } return 0; } ----------------- fin del programa 6.1 ---------------------
Debido a que en la versión con punteros es necesaria una doble desreferenciación, con frecuencia se dice que el nombre de un array de dos dimensiones es lo mismo que un puntero a un puntero. En un array de tres dimensiones tendríamos un array de arrays de arrays y algunos dicen que su nombre es lo mismo que un puntero a un puntero a un puntero. Sin embargo, aquí hemos inicializado el bloque de memoria utilizando la notación de arrays, por tanto el array es una constante, no una variable. Es decir, estamos hablando de una dirección fija en memoria, no de un puntero que puede apuntar de forma variable. La desreferencia permite acceder a cualquier elemento del array de arrays sin cambiar el valor de esa dirección (tanto la dirección de multi[0][0] como la dirección del símbolo multi).