Punteros:
Los punteros son variables que contienen la dirección de una posición de memoria, que típicamente almacenará otra variable, pero que también puede ser la dirección de memoria de un registro, por ejemplo, un registro del VIC, la dirección del comienzo de la pantalla ($0400), o lo que sea.
El operador & sirve para obtener la dirección de memoria de una variable. Por ejemplo:
int variable1;
int *puntero = &variable1;
En el caso particular del C64, como las direcciones son de 16 bits (desde $0000 hasta $ffff), y como los int de cc65 también ocupan 16 bits, una dirección o puntero viene ser básicamente lo mismo que un int.
Pero, más allá de lo anterior (que un puntero es o almacena una dirección de memoria), al nivel de C, un “puntero” no es una dirección arbitraria sin más, sino que es una dirección que apunta a un double, o a un char, o a un int, etc. Adonde quiero llegar, los punteros en C tienen un tipo asociado, son punteros que apuntan a un tipo de variable concreto. Esto es lo que luego va a permitir la “aritmética de punteros”, es decir, va a permitir hacer cosas como:
puntero = puntero + 1;
sabiendo que ese +1 no es la dirección o el byte siguiente en sentido literal, sino la posición siguiente tiendo en cuenta el tamaño de las variables apuntadas, que en el caso de un char es 1 byte, pero que en el caso de otros tipos serán tamaños mayores.
Por otro lado, el operador * es el operador de indirección, es decir, aplicado sobre un puntero nos da el contenido de la variable o posición de memoria apuntada por él:
int variable2 = *puntero;
También es posible hacer a la inversa, es decir, almacenar cierto contenido en la posición o variable apuntada por un puntero:
*puntero = variable2;
E, incluso, es posible asignar valores entre punteros, es decir, conseguir que varios punteros apunten a la misma dirección:
puntero1 = puntero2;
Por último, en ocasiones es necesario o conveniente indicar que un puntero no apunta a nada, o no apunta a nada válido. En tal caso, puede usarse la dirección 0 o, lo que es lo mismo, la constante simbólica NULL definida en <stdio.h>.
Punteros y funciones:
Ya dijimos en su momento que los argumentos o parámetros de las funciones en C se pasan “por valor”, es decir, en una función del tipo int doble(int x), cuando se llama con doble(x), si x vale 7, la función tiene una variable local también llamada x, que inicialmente vale 7, pero que es independiente de la variable x del programa o función llamante. Este “paso por valor” es la regla general.
Ahora bien, el caso de los punteros es especial. Los punteros también pueden ser parámetros de funciones y, como tales punteros, también se pasan “por valor”, es decir, la función tendrá otro puntero local con la misma dirección.
Pero como este segundo puntero, el local a la función, apunta a la misma dirección que el argumento pasado, en el fondo, a través de esa dirección, es posible modificar el valor de la variable apuntada. Esto es lo que se llama “paso por referencia”, porque lo que sea pasa en realidad es una referencia o puntero a la variable original.
En definitiva, cuando en C se pretende que una función pueda modificar una variable pasada a una función, el paso tiene que ser por puntero o referencia.
Otra cuestión interesante, relativa a punteros y funciones, es que, al ser un puntero en esencia una dirección, esa dirección no sólo puede apuntar a una variable, sino a una posición de memoria arbitraria (ej. un registro del VIC o del SID). En particular, también puede apuntar a una dirección de memoria que tenga código ejecutable, no datos. Es decir, que un puntero pueda apuntar al comienzo de una función. Sabiendo ensamblador esto se ve muy claro, ya que una posición de memoria (o una etiqueta de un macroensamblador) puede contener datos o código ejecutable.
Esto permite en C hacer cosas bastante abstractas, como algoritmos de ordenación (ordenar básicamente es comparar elementos e intercambiarlos si es necesario) en los que es posible cambiar, mediante punteros a diferentes funciones, la función de comparación y/o la función de intercambio.
Arrays:
Los arrays son posiciones consecutivas de memoria que almacenan una secuencia de valores del mismo tipo. El array se maneja o referencia mediante el puntero a su primera posición. De ahí la relación estrecha entre punteros y arrays en C.
Por tanto, los punteros y los arrays en C son construcciones muy parecidas, siendo la principal diferencia que los arrays admiten una sintaxis específica, por ejemplo:
unsigned char buffer[10];
Esto declara un array llamado “buffer” que tiene 10 posiciones, y cada una de esas 10 posiciones almacena un unsigned char, es decir, un byte.
Además, si se quiere acceder a la posición i-ésima del array esto se puede hacer con un índice:
unsigned char mi_char;
mi_char = buffer[i];
Pero la relación entre arrays y punteros no se limita a la primera posición del array (ej. puntero = &buffer[0]). Un puntero del tipo adecuado puede apuntar a cualquier posición intermedia del array (ej. puntero = &buffer[i]) e, incluso, mediante aritmética de punteros (ej. puntero = puntero+1), es posible recorrer el array y acceder a los valores de las diferentes posiciones (ej. *(puntero+1)). Y todo esto, además, teniendo en cuenta que ese +1 no se refiere literalmente al byte siguiente, sino a la posición siguiente teniendo en cuenta el tamaño (1 byte, 2 bytes, 3 bytes, etc.) de las posiciones del array.
Los arrays, de hecho, cuando son parámetros de funciones se pasan por referencia, puesto que en el fondo el nombre del array equivale a un puntero a su primera posición.
Cadenas de caracteres:
Las cadenas de caracteres en C son arrays de caracteres que terminan con el carácter cero (‘\0’). Por tanto, la cadena “Hola mundo” equivale al array de caracteres:
Hola mundo\0
La principal novedad, como ya se ha visto, es que las cadenas de caracteres tienen una sintaxis especial para poder ser definidas:
char cadena1[] = “Hola mundo”;
De hecho, como el tamaño del array está implícito (10 caracteres + 1 carácter por el 0 final), ni siquiera hace falta especificarlo. Esto viene a ser equivalente a la sintaxis para inicializar arrays basada en llaves:
char cadena2[] = {‘H’, ‘o’, ‘l’, ‘a’, ‘ ’, ‘m’, ‘u’, ‘n’, ‘d’, ‘o’, ‘\0’};
Las principales diferencias son que se usan comillas en vez de llaves y que, con comillas, no hace falta especificar el ‘\0’ final. Se da por hecho que es una cadena de caracteres y, por tanto, que termina con el carácter cero.
En definitiva, los punteros y los arrays son construcciones muy parecidas en C, y las cadenas de caracteres no dejan de ser arrays (de caracteres) que terminan con el carácter cero.
Paso de parámetros a un programa en C:
Ahora que sabemos cómo funcionan los arrays y las cadenas, la forma de hacer llegar uno o varios parámetros a un programa en C, es decir, a su función main(), es mediante los parámetros opcionales argc y argv[]:
void main(int argc, char *argv[]) {…}
El entero argc nos dice el número de parámetros o argumentos efectivamente pasados, y el array argv[] nos da acceso a esos argumentos, que en el fondo son cadenas de texto, es decir, punteros a caracteres (char *).
Por último, recordemos que la forma de ejecutar un programa cc65 y pasarle parámetros es con la sintaxis:
RUN : REM ARG1 “ARG 2” ARG3 “ARG 4” …
Código de ejemplo: punteros