Apuntadores en C y C

++
Universidad de Carabobo Facultad Experimental de Ciencias y Tecnolog´ ıa Prof. Marcos A. Gil T. 8 de diciembre de 2004

1.

Introducci´ on

Los apuntadores en C y C++ son una herramienta muy potente de programaci´ on que suele causar mucha confusi´ on en los estudiantes que la est´ an aprendiendo. Adem´ as, cuando los programadores cometen un error en su utilizaci´ on, puede ser muy dif´ ıcil encontrar el error, por lo cual es importante saber utilizarlos muy bien. El uso de apuntadores en C y C++ es muy importante debido a que permite hacer los programas m´ as eficientes y m´ as flexibles. En en este art´ ıculo se explica de una manera sencilla y breve todo lo referente a la utilizaci´ on de apuntadores tanto en C como en C++. Todo lo explicado en este art´ ıculo aplica tanto para C como para C++, a menos que se especifique un lenguaje en particular. En algunos ejemplos de c´ odigo que son aplicables a C aparecen instrucciones de entrada y salida de las librer´ ıas est´ andar de C++.

2.

Definici´ on de apuntador

Cuando se declara una variable, el compilador reserva un espacio de memoria para ella y asocia el nombre de ´ esta a la direcci´ on de memoria desde donde comienzan los datos de esa variable. Las direcciones de memoria se suelen describir como n´ umeros en hexadecimal. Un apuntador es una variable cuyo valor es la direcci´ on de memoria de otra variable. Se dice que un apuntador “apunta” a la variable cuyo valor se almacena a partir de la direcci´ on de memoria que contiene el apuntador. Por ejemplo, si un apuntador p almacena la direcci´ on de una variable x, se dice que “p apunta a x ”.

3.

Referenciaci´ on

La referenciaci´ on es la obtenci´ on de la direcci´ on de una variable. En C y C++ esto se hace a trav´ es del operador ‘&’, aplicado a la variable a la cual se desea saber su direcci´ on. N´ otese que se trata de un operador unario. Ejemplo: C´ odigo C y C++ int x = 25; cout << "La direcci´ on de x es: " << &x << endl;

Este c´ odigo imprime un valor del estilo “0x4fffd34”. Este valor puede variar durante cada ejecuci´ on del programa, debido a que el programa puede reservar distintos espacios de memoria durante cada ejecuci´ on.

La sintaxis es la siguiente: <tipo de dato apuntado> *<indentificador del apuntador> A continuaci´ on se muestran varios ejemplos: C´ odigo C y C++ int *ptr1.4. // Se le asigna a ’p’ la direcci´ on de ’i’ // Se le asigna a ’q’ la direcci´ on almacenada en ’p’ (la misma de ’i’) 6. Ejemplo: 2 . p = &i. Muchos compiladores de C tambi´ en soportan este operador. En C y C++ esto se hace a trav´ es del operador ‘*’. // A ’y’ se le asigna 20 C++ adem´ as provee el operador binario ‘->’. // Apuntador a un dato de tipo punto-flotante (float) 5. // Imprime 17 y = *p + 3. *cad2. Asignaci´ on de apuntadores Se pueden asignar a un apuntador direcciones de variables a trav´ es del operador de referenciaci´ on (‘&’) o direcciones almacenadas en otros apuntadores. int *p. cout << "El valor de x es: " << *p << endl. q = p. Ejemplos: C´ odigo C y C++ int x = 17. utilizado para obtener campos de un registro con un apuntador al mismo de una manera m´ as f´ acil y legible. Desreferenciaci´ on de apuntadores La desreferenciaci´ on es la obtenci´ on del valor almacenado en el espacio de memoria donde apunta un apuntador. int *p. N´ otese que se trata de un operador unario. y. p = &x. // Apuntador a un dato de tipo entero (int) char *cad1. Declaraci´ on de apuntadores Para declarar un apuntador se especifica el tipo de dato al que apunta. Ejemplos: C´ odigo C y C++ int i = 5. // Dos apuntadores a datos de tipo car´ acter (char) float *ptr2. y el nombre del apuntador. aplicado al apuntador que contiene la direcci´ on del valor. el operador ‘*’. *q.

C´ odigo C++ struct Data { char nombre[20]. Direcciones inv´ alidas y la direcci´ on NULL Normalmente. int x. al igual que cualquier otra variable. en cuyo caso es incorrecto desreferenciarlo (obtener el valor al que apunta) porque el programa tendr´ a un comportamiento impredecible y probablemente err´ oneo. Data d. Verificaci´ on de tipos en apuntadores Al igual que el resto de las variables. p1 = &x. un apuntador inicializado adecuadamente apunta a alguna posici´ on espec´ ıfica de la memoria. Ejemplo: C´ odigo C y C++ int *p1. Despu´ es de que un apuntador ha sido inicializado. float *p2. int edad. // Acceso al campo ’edad’ utilizando el operador ’. Un apuntador puede contener una direcci´ on inv´ alida debido a dos razones: 1. Ejemplo: 1 La asignaci´ on din´ amica de memoria se explica en la secci´ on 13 3 . Sin embargo. de manera que a un apuntador s´ olo se le pueden asignar direcciones de variables del tipo especificado en la declaraci´ on del apuntador. ya sea porque la variable asociada termina su ´ ambito o porque ese espacio de memoria fue reservado din´ amicamente y luego se liber´ o1 .edad = 23. la direcci´ on que posee puede dejar de ser v´ alida si se libera la memoria reservada en esa direcci´ on. Ejemplo: C´ odigo C y C++ float *p. (*pd).5. // Esto no es v´ alido (el compilador genera un error) 8. // Acceso al campo ’edad’ utilizando el operador ’->’ 7. aunque es posible que funcione bien. // Incorrecto *p = 3. los apuntadores se enlazan a tipos de datos espec´ ıficos (apuntadores a variables de cierto tipo). el mismo posee un valor cualquiera que no se puede conocer con antelaci´ on. cout << "El valor apuntado por p es: " << *p << endl. Cuando un apuntador se declara. algunas veces es posible que un apuntador no contenga una direcci´ on v´ alida. // Incorrecto 2. }. // Esto es v´ alido p2 = &x.’ pd->edad = 23. Data *pd = &d. hasta que se inicialice con alg´ un valor (direcci´ on).

y = *p. en cuyo caso detiene abruptamente el programa. En muchos casos el sistema operativo detecta el acceso inadecuado a una direcci´ on de memoria. void func() { int x = 40. etc. en la zona donde se almacenan las instrucciones del programa. se le suele asignar el valor NULL. NULL es una macro t´ ıpicamente definida en archivos de cabecera como stdef. y. en cuyo caso se dice que el apuntador es nulo (no apunta a nada). f´ acilmente puede ocurrir que el programa genere un error de ejecuci´ on y el sistema operativo lo detenga. matrices esparcidas. Apuntadores a apuntadores Dado que un apuntador es una variable que apunta a otra. Es igualmente incorrecto desreferenciar el valor NULL por las mismas razones presentadas previamente. p = &x. existe el riesgo de sobreescribir datos de otras variables. Cuando no se desea que un apuntador apunte a algo. f´ acilmente se puede deducir que pueden existir apuntadores a apuntadores. // Correcto *p = 23. // Incorrecto *p = 25. Normalmente.C´ odigo C y C++ int *p. Existe la posibilidad de que la direcci´ on est´ e fuera de la zona de memoria utilizada para almacenar datos y m´ as bien est´ e. NULL se suele definir en estas librer´ ıas as´ ı: C´ odigo C y C++ #define NULL 0 Un apuntador nulo se utiliza para proporcionar a un programa un medio de conocer cu´ ando un apuntador contiene una direcci´ on v´ alida. Si casualmente la direcci´ on es la misma de otra variable utilizada en el programa. y as´ ı sucesivamente. o que el programa no responda y deje al sistema operativo inestable. 9. // Correcto } void main() { func(). como las listas enlazadas. Se suele utilizar un test condicional para saber si un apuntador es nulo o no lo es.h y stdlib. por ejemplo. y a su vez los segundos pueden apuntar a apuntadores. Esto puede ocasionar que el programa genere resultados incorrectos. y tomar las medidas necesarias. o est´ a dentro del rango de direcciones de una zona de memoria utilizada. // Incorrecto } Si se intenta desreferenciar un apuntador que contiene una direcci´ on inv´ alida pueden ocurrir cosas como las siguientes: Se obtiene un valor incorrecto en una o m´ as variables debido a que no fue debidamente inicializada la zona de memoria que se accede a trav´ es de la direcci´ on en cuesti´ on. El valor NULL es muy u ´til para la construcci´ on de estructuras de datos din´ amicas. en C++ se encuentra disponible sin incluir ning´ un archivo de cabecera.h. Al intentar escribir en dicha zona. y = *p. Estos 4 .

Apuntadores constantes y apuntadores a constantes Es posible declarar apuntadores constantes. El nombre de un arreglo es simplemente un apuntador constante al inicio del arreglo. // A partir de aqu´ ı ambas variables apuntan al mismo sitio cout << lista_arr[0]. Ejemplo: C´ odigo C y C++ int x = 5. // Instrucci´ on equivalente a la anterior cout << *lista_arr. // Esto es v´ alido p = &y. Apuntadores. Se pueden direccionar arreglos como si fueran apuntadores y apuntadores como si fueran arreglos. int *const p = &x. // Imprime 40 cout << lista_ptr[3]. Ejemplo: C´ odigo C y C++ char c = ’z’. 50}. no se permite la modificaci´ on de la direcci´ on almacenada en el apuntador. // Esto es v´ alido *p = 3. // Declaraci´ on e inicializaci´ on del apuntador a constante p = &y. 30. 40. y = 7. // Esto es v´ alido 11. Esto permite recorrer arreglos utilizando apuntadores. Ejemplos: C´ odigo C y C++ int lista_arr[5] = {10. Esto hace que no sea posible modificar el valor al que apunta el apuntador. 20. int *lista_ptr. y = 7. arreglos y aritm´ etica de apuntadores Los arreglos y apuntadores est´ an fuertemente relacionados. El resultado de estas operaciones es el desplazamiento de la direcci´ on de memoria hacia adelante (suma) o hacia atr´ as (resta) por bloques de bytes del tama˜ no del tipo de dato apuntado por el apuntador. lista_ptr = lista_arr. Ejemplo: C´ odigo C y C++ int x = 5. ***pppc = ’m’. pero s´ ı se permite la modificaci´ on del valor al que apunta. Ejemplos: 5 . // Esto no es v´ alido (el compilador genera un error) Tambi´ en es posible declarar apuntadores a datos constantes. // Declaraci´ on e inicializaci´ on del apuntador constante *p = 3. // Esto no es v´ alido (el compilador genera un error) y = 3. // Instrucci´ on equivalente a la anterior Es posible sumar y restar valores enteros a un apuntador. char **ppc = &pc. // Instrucci´ on equivalente a la anterior cout << lista_arr[3]. // Instrucci´ on equivalente a la anterior cout << *lista_ptr. char ***pppc = &ppc. // Cambia el valor de c a ’m’ 10. char *pc = &c. De esta manera. // Imprime 10 cout << lista_ptr[0].apuntadores se declaran colocando tantos asteriscos (‘*’) como sea necesario. const int *p = &x.

} void main() { int x. El resultado de esta operaci´ on es el n´ umero de bloques de bytes que hay entre las dos direcciones del tama˜ no del tipo de dato apuntado por los apuntadores.1. es posible hacerlo a trav´ es del uso de apuntadores. p = &lista[3].1. cout << *(lista+2). 20.p.1}. Tambi´ en es posible restar dos apuntadores. 50}. Sin embargo.1. p = lista + 3. 40. y luego un c´ odigo equivalente en C o C++ utilizando un apuntador: C´ odigo C++ void suma(int a.1. char cad[15]. int n. int b. Ejemplo: C´ odigo C y C++ double x[5] = {1. *q = &x[4]. Instrucci´ on equivalente a la anterior // Las siguientes instrucciones imprimen la palabra "Programando" /* Nota: Recu´ erdese que una constante de cadena de caracteres es una secuencia de caracteres en memoria seguidos del c´ aracter nulo */ strcpy(cad. int& r) { r = a + b. 5. int *p. 4. x). 30. A continuaci´ on se muestra un ejemplo del paso de un par´ ametro por referencia en C++. 5. } 6 . // // // // ’p’ almacena la direcci´ on de la posici´ on 3 del arreglo Instrucci´ on equivalente a la anterior Imprime 30. double *p = &x[1].C´ odigo C y C++ int lista[5] = {10. cout << "7 + 5 = " << x. "Programando"). 2. n = q . Apuntadores para paso de par´ ametros por referencia El lenguaje C no provee una manera de pasar par´ ametros por referencia. // a ’n’ se le asigna 3 12. 3. *q != ’\0’. for (q = cad. cout << lista[2]. char *q. q++) cout << q. suma(7.

La sintaxis de ambas funciones es como sigue: C´ odigo C y C++ void *malloc(size_t tam_bloque). Las variables locales se almacenan en la pila (stack ) y existen s´ olo mientras est´ an activas las funciones donde est´ an declaradas. Ejemplos: 7 . int *r) { *r = a + b. // size_t es un tipo de datos entero. retornando un apuntador a void. definidas t´ ıpicamente en el archivo de cabecera stdlib.1. y la funci´ on free() permite liberar la memoria reservada a trav´ es de un apuntador. 13. En C est´ an disponibles varias funciones que permiten realizar reservar y librerar memoria. El operador ‘&’ tiene dos significados como operador unario: se˜ nalaci´ on de par´ ametro por referencia y operador de referenciaci´ on. pero C++ adem´ as provee un m´ etodo m´ as f´ acil y seguro de hacerlo. Asignaci´ on din´ amica de memoria Los programas pueden crear variables globales o locales. &x). En ambos casos el espacio de almacenamiento se reserva en el momento de la compilaci´ on del programa. La funci´ on malloc() reserva memoria y retorna su direcci´ on. Asignaci´ on din´ amica de memoria al estilo de C Para asignar memoria din´ amicamente en C se utilizan las funciones malloc() y free(). Como se puede apreciar. Por esta raz´ on. void free(void *bloque). Tambi´ en es posible reservar y utilizar memoria din´ amicamente. } void main() { int x.h. o retorna NULL en caso de no haber conseguido suficiente memoria. // bloque es un apuntador a la zona // de memoria a liberar. en la zona conocida como segmento de datos del programa.C´ odigo C y C++ void suma(int a. malloc() reserva memoria sin importar el tipo de datos que se almacenar´ a en ella. suma(7. } N´ otese que en ambos casos se utiliza el operador ‘&’ para cosas distintas. ayud´ andose con el uso del operador sizeof. Tambi´ en es necesario calcular exactamente cu´ antos bytes se requieren reservar. y todas las funciones pueden utilizar estas variables. 5. 13. int b. tomada de la zona de memoria llamada mont´ ıculo (heap ) o almac´ en libre. se hace necesario convertir el tipo de apuntador al tipo del apuntador que guardar´ a la direcci´ on. cout << "7 + 5 = " << x. // tam_bloque es el tama~ no en bytes // del bloque de memoria a reservar. Las variables declaradas globales en sus programas se almacenan en posiciones fijas de memoria.

o retorna NULL en caso de no haber conseguido suficiente memoria.C´ odigo C y C++ typedef struct { char nombre[20]. i < 10. Data *p_data. De no ser as´ ı. // Liberaci´ on de memoria del arreglo free(p_data). } // Verificaci´ on de reservaci´ on // Inicializaci´ on de datos // en la memoria reservada // Liberaci´ on de memoria 13. "Rachel"). // Declaraci´ on de un apuntador a Data int i. free(p_data). y el operador delete permite liberar la memoria reservada a trav´ es de un apuntador. Ejemplos: 8 .nombre >> p_data[i]. Asignaci´ on din´ amica de memoria al estilo de C++ Para asignar memoria din´ amicamente en C++ se utilizan los operadores new y delete. p_data->edad = 21. } Data.2. i++) cin >> p_data[i]. if (p_data != NULL) // Verificaci´ on de reservaci´ on { // Lectura de datos del arreglo for (i = 0.edad. p_data = (Data*) malloc(sizeof(Data)). se produce un error de compilaci´ on. El operador new reserva memoria para un tipo de datos espec´ ıfico y retorna su direcci´ on. // Reservaci´ on de memoria para un registro if (p_data != NULL) { strcpy(p_data->nombre. int edad. } // Reservaci´ on de memoria para un arreglo de 10 registros p_data = (Data*) malloc(sizeof(Data)*10). La sintaxis de ambos operadores es como sigue: Para reservar y liberar un solo bloque: <apuntador> = new <tipo de dato> delete <apuntador> Para reservar y liberar varios bloques (un arreglo): <apuntador> = new <tipo de dato>[<n´ umero de bloques>] delete [] <apuntador> El tipo del apuntador especificado del lado izquierdo del operador new debe coincidir con el tipo especificado del lado derecho.

// Declaraci´ on de un apuntador a Data int i. p_data->edad = 21. }. i++) cin >> p_data[i]. } // Verificaci´ on de reservaci´ on // Inicializaci´ on de datos // en la memoria reservada // Liberaci´ on de memoria 14. i < 10. p_data = new Data. // Liberaci´ on de memoria del arreglo delete [] p_data.nombre >> p_data[i]. 9 .edad. "Rachel"). } // Reservaci´ on de memoria para un arreglo de 10 registros p_data = new Data[10]. delete p_data. del autor Luis Joyanes Aguilar. editorial Mc Graw Hill. Data *p_data. int edad.C´ odigo C++ struct Data { char nombre[20]. if (p_data != NULL) // Verificaci´ on de reservaci´ on { // Lectura de datos del arreglo for (i = 0. // Reservaci´ on de memoria para un registro if (p_data != NULL) { strcpy(p_data->nombre. Bibliograf´ ıa El presente art´ ıculo fue realizado con la ayuda del libro “Programaci´ on en C++ ”.

Sign up to vote on this title
UsefulNot useful