Universidad Carlos III de Madrid

Grado en Ing. Telemática/Sist. Audiovisuales/Sist. de Comunicaciones

Arquitectura de Sistemas

Septiembre 2012 - Enero 2013

5. La indirección a través de punteros

Los punteros tienen dos cometidos. El primero es almacenar una dirección de memoria (ver sección 4). El segundo es utilizar la dirección de memoria almacenada para acceder al dato al que apunta mediante una indirección (ver sección 2 es una indirección).

En C la indirección se denota con el operador * seguido del nombre de una variable de tipo puntero. Su significado es accede al contenido al que apunta el puntero. Desafortunadamente, este operador coincide con el utilizado para denotar los tipos de datos punteros y para declarar este tipo de variables. Es muy importante tener una distinción muy clara entre estos dos usos del operador. Al traducir un programa, el compilador no comprueba ni que el puntero contiene una dirección de memoria correcta, ni que en esa dirección de memoria hay un dato correcto. Esta característica hace que el diseño de programas con punteros sea complejo.

El siguiente programa muestra el uso del operador de indirección (fichero pointer_example_2.c):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main(int argc, char** argv) 
{
    int num1, num2;
    int *ptr1, *ptr2;

    ptr1 = &num1;
    ptr2 = &num2;

    num1 = 10;
    num2 = 20;

    *ptr1 = 30;
    *ptr2 = 40;

    *ptr2 = *ptr1;

    return 0;
}

La línea 13 asigna el valor 30 a la dirección de memoria almacenada en ptr1 mediante una indirección. Como el puntero tiene la dirección de num1 (asignada en la línea 7) esta linea es análoga a asignar el valor 30 directamente a num1. Análogamente, la línea 14 asigna el valor 40 a la dirección a la que apunta ptr2. Esta asignación es equivalente a asignar el valor 40 a num2. La línea 16 contiene dos indirecciones. La expresión a la derecha del signo igual obtiene el dato en la posición indicada por la dirección almacenada en ptr1 (la dirección de num1) y este dato se almacena en la posición indicada por el puntero ptr2 (la dirección de num2). Al terminar el programa, la variable num2 contiene el valor 30 a pesar de que no ha sido asignado directamente.

La siguiente figura muestra la evolución de estas cuatro variables a lo largo del programa. De nuevo, las direcciones de memoria en las que están almacenadas son arbitrarias.

La coincidencia entre el operador de indirección y el de definición de tipo puntero * puede dificultar la comprensión del código. Por ejemplo, la siguiente línea

int *r = *p;

contiene dos operandos *. El primero se define el tipo puntero a entero y va seguido de la declaración de la variable r. El segundo es una indirección que se aplica a la dirección almacenada en el puntero p.

5.1. Acceso indirecto a campos de una estructura

Supongamos que se ha definido una estructura con nombre struct s. Aplicando la regla de definición de tipo puntero, un puntero a esta estructura tiene el tipo struct s *. En el siguiente fragmento de código se define y declara una estructura, y luego se declara un puntero que se inicializa a su dirección.

1
2
3
4
5
6
7
8
9
10
struct s 
{
    int x;
    char c;
}

struct s element;
struct s *pointer;

pointer = &element;

Cuando se realiza una indirección con pointer es sólo para acceder a los campos de la estructura. Esta operación requiere dos operadores, la indirección (operador * y el acceso a los campos de una estructura (operador .). La sintaxis es:

(*pointer).x = 10;
(*pointer).c = 'l';

Estas dos operaciones en C se agrupan en el operador -> que se sitúa entre el puntero y el nombre del campo de la estructura. La notación equivalente, por tanto, es:

pointer->x = 10;
pointer->c = 'l';

El operador -> se escribe siempre a continuación de un puntero que apunta a una estructura, y precede al nombre de uno de los campos de esa estructura. En el siguiente programa se muestra como se pueden copiar los datos de una estructura a otra utilizando este operador.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Definición de la estructura */
struct coordenadas 
{
    float x;
    float y;
    float z;
};

int main() 
{
    /* Declaración de dos estructuras */
    struct coordenadas location1, location2;
    /* Declaración de dos punteros */
    struct coordenadas *ptr1, *ptr2;

    /* Asignación de direcciones a los punteros */
    ptr1 = &location1;
    ptr2 = &location2;

    /* Asignación de valores a la primera estructura */
    ptr1->x = 3.5;
    ptr1->y = 5.5;
    ptr1->z = 10.5;

    /* Copia de valores a la segunda estructura */
    ptr2->x = ptr1->x;
    ptr2->y = ptr1->y;
    ptr2->z = ptr1->z;
  
    return 0;
}

Sugerencia

Copia y pega el contenido del programa anterior en un fichero de texto en tu entorno de desarrollo. Compila y ejecuta. Define una segunda estructura e incluye un puntero a ella como campo de la estructura coordenada. Escribe código para manipular esta nueva estructura.

5.2. Preguntas de autoevaluación

  1. El puntero a contiene la dirección de memoria del puntero b que contiene la dirección de memoria del entero c. ¿Cual de las siguientes expresiones le asigna el valor 30 al entero c?

    • *a = 30

    • **a = 30

    • ***a = 30

    • No se puede hacer esa asignación

  2. Considera el siguiente fragmento de código:

    struct data
    {
        int *ptr;
        int num;
    } a;
    
    a.ptr = &(a.num);

    ¿Cuál de las siguientes expresiones asigna el valor 10 al campo num de la estructura a?

    • *(a.ptr) = 10

    • a.ptr = 10

    • a.*ptr = 10

    • a.ptr = *10

    • La asignación no se puede hacer utilizando sólo el campo ptr.