Buscar..


Introducción

Un puntero es un tipo de variable que puede almacenar la dirección de otro objeto o una función.

Sintaxis

  • <Tipo de datos> * <Nombre de variable>;
  • int * ptrToInt;
  • void * ptrToVoid; / * C89 + * /
  • struct someStruct * ptrToStruct;
  • int ** ptrToPtrToInt;
  • int arr [longitud]; int * ptrToFirstElem = arr; / * Para <C99 'length' debe ser una constante de tiempo de compilación, para> = C11 podría ser una. * /
  • int * arrayOfPtrsToInt [longitud]; / * Para <C99 'length' debe ser una constante de tiempo de compilación, para> = C11 podría ser una. * /

Observaciones

La posición del asterisco no afecta el significado de la definición:

/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;

Sin embargo, al definir varios punteros a la vez, cada uno requiere su propio asterisco:

int *i, *j; /* i and j are both pointers */
int* i, j;  /* i is a pointer, but j is an int not a pointer variable */

También es posible una matriz de punteros, donde se da un asterisco antes del nombre de la variable de matriz:

int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */

Errores comunes

El uso incorrecto de los punteros es a menudo una fuente de errores que pueden incluir errores de seguridad o fallas en los programas, la mayoría de las veces debido a fallas de segmentación.

No revisar fallas en la asignación

No se garantiza que la asignación de memoria tenga éxito y, en cambio, puede devolver un puntero NULL . El uso del valor devuelto, sin verificar si la asignación es exitosa, invoca un comportamiento indefinido . Por lo general, esto conduce a un choque, pero no hay garantía de que ocurra un choque, por lo que confiar también puede ocasionar problemas.

Por ejemplo, de manera insegura:

struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */

Camino seguro:

struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
    s->someValue = 0; /* This is safe, we have checked that s is valid */
}

Usando números literales en lugar de sizeof al solicitar memoria

Para una configuración de compilador / máquina dada, los tipos tienen un tamaño conocido; sin embargo, no hay ninguna norma que defina que el tamaño de un tipo dado (que no sea char ) sea el mismo para todas las configuraciones de compilador / máquina. Si el código usa 4 en lugar de sizeof(int) para la asignación de memoria, puede funcionar en la máquina original, pero el código no es necesariamente portátil a otras máquinas o compiladores. Los tamaños fijos para los tipos deben reemplazarse por sizeof(that_type) o sizeof(*var_ptr_to_that_type) .

Asignación no portátil:

 int *intPtr = malloc(4*1000);    /* allocating storage for 1000 int */
 long *longPtr = malloc(8*1000);  /* allocating storage for 1000 long */

Asignación portátil:

 int *intPtr = malloc(sizeof(int)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(long)*1000);  /* allocating storage for 1000 long */

O, mejor aún:

 int *intPtr = malloc(sizeof(*intPtr)*1000);     /* allocating storage for 1000 int */
 long *longPtr = malloc(sizeof(*longPtr)*1000);  /* allocating storage for 1000 long */

Pérdidas de memoria

Si no se desasigna la memoria utilizando free cables free acumula una memoria no reutilizable que el programa ya no utiliza. Esto se llama una pérdida de memoria . La memoria pierde recursos de memoria de desperdicio y puede conducir a fallas en la asignación.

Errores logicos

Todas las asignaciones deben seguir el mismo patrón:

  1. Asignación utilizando malloc (o calloc )
  2. Uso para almacenar datos
  3. Desasignación utilizando free

Si no se adhiere a este patrón, como el uso de la memoria después de una llamada para free ( puntero colgante ) o antes de una llamada a malloc ( puntero salvaje ), llamar dos veces free ("doble libre"), etc., generalmente causa un fallo de segmentación y los resultados en una caída del programa.

Estos errores pueden ser transitorios y difíciles de depurar; por ejemplo, el sistema operativo generalmente no recupera la memoria liberada, por lo que los punteros colgantes pueden persistir por un tiempo y parecer que funcionan.

En los sistemas donde funciona, Valgrind es una herramienta invaluable para identificar qué memoria se ha filtrado y dónde se asignó originalmente.

Creando punteros para apilar variables.

Crear un puntero no prolonga la vida de la variable a la que se apunta. Por ejemplo:

int* myFunction() 
{
    int x = 10;
    return &x;
}

Aquí, x tiene una duración de almacenamiento automático (comúnmente conocida como asignación de pila ). Debido a que está asignado en la pila, su vida útil es solo mientras myFunction esté ejecutando; después de que myFunction haya salido, la variable x se destruye. Esta función obtiene la dirección de x (usando &x ) y la devuelve a la persona que llama, dejando a la persona que llama con un puntero a una variable que no existe. Intentar acceder a esta variable invocará un comportamiento indefinido .

La mayoría de los compiladores en realidad no borran un marco de pila después de que la función sale, por lo tanto, al eliminar la referencia al puntero devuelto a menudo se obtienen los datos esperados. Sin embargo, cuando se llama a otra función, la memoria a la que se apunta puede sobrescribirse, y parece que los datos a los que se apunta están dañados.

Para resolver esto, malloc un malloc el almacenamiento de la variable que se devolverá y devuelva un puntero al almacenamiento recién creado, o requiera que se pase un puntero válido a la función en lugar de devolver uno, por ejemplo:

#include <stdlib.h>
#include <stdio.h>

int *solution1(void) 
{
    int *x = malloc(sizeof *x);
    if (x == NULL) 
    {
        /* Something went wrong */
        return NULL;
    }

    *x = 10;

    return x;
}

void solution2(int *x) 
{
    /* NB: calling this function with an invalid or null pointer 
       causes undefined behaviour. */

    *x = 10;
}

int main(void) 
{
    { 
        /* Use solution1() */

        int *foo = solution1();  
        if (foo == NULL)
        {
            /* Something went wrong */
            return 1;
        }

        printf("The value set by solution1() is %i\n", *foo);
        /* Will output: "The value set by solution1() is 10" */

        free(foo);    /* Tidy up */
    }

    {
        /* Use solution2() */

        int bar;
        solution2(&bar); 

        printf("The value set by solution2() is %i\n", bar);
        /* Will output: "The value set by solution2() is 10" */
    }

    return 0;
}

Incremento / decremento y desreferenciación

Si escribe *p++ para incrementar lo que apunta p , está equivocado.

El incremento / decremento posterior se ejecuta antes de la desreferenciación. Por lo tanto, esta expresión incrementará el puntero p sí mismo y devolverá lo que fue apuntado por p antes de incrementarlo sin cambiarlo.

Debería escribir (*p)++ para incrementar lo que apunta p .

Esta regla también se aplica al decremento posterior: *p-- disminuirá el puntero p sí, no lo que se señala con p .

Desreferenciación de un puntero

int a = 1;
int *a_pointer = &a;

Para a_pointer referencia a a_pointer y cambiar el valor de a, usamos la siguiente operación

*a_pointer = 2;

Esto se puede verificar usando las siguientes declaraciones impresas.

printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */

Sin embargo, uno podría confundirse con la desreferencia de un puntero NULL o no válido. Esta

int *p1, *p2;

p1 = (int *) 0xbad;
p2 = NULL;

*p1 = 42;
*p2 = *p1 + 1;

Generalmente es un comportamiento indefinido . p1 no puede ser desreferenciado porque apunta a una dirección 0xbad que puede no ser una dirección válida. ¿Quién sabe qué hay ahí? Puede ser la memoria del sistema operativo, o la memoria de otro programa. El único código de tiempo como este que se usa es el desarrollo integrado, que almacena información particular en direcciones codificadas. p2 no se puede anular debido a que es NULL , lo cual no es válido.

Desreferenciación de un puntero a una estructura

Digamos que tenemos la siguiente estructura:

struct MY_STRUCT 
{
    int my_int;
    float my_float;
};

Podemos definir MY_STRUCT para omitir la palabra clave struct por lo que no tenemos que escribir struct MY_STRUCT cada vez que lo usamos. Esto, sin embargo, es opcional.

typedef struct MY_STRUCT MY_STRUCT;

Si entonces tenemos un puntero a una instancia de esta estructura

MY_STRUCT *instance;

Si esta declaración aparece en el ámbito del archivo, la instance se inicializará con un puntero nulo cuando se inicie el programa. Si esta declaración aparece dentro de una función, su valor no está definido. La variable debe inicializarse para que apunte a una variable MY_STRUCT válida, o al espacio asignado dinámicamente, antes de que se pueda eliminar la referencia. Por ejemplo:

MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;

Cuando el puntero es válido, podemos anularlo para acceder a sus miembros usando una de dos notaciones diferentes:

int a = (*instance).my_int;
float b = instance->my_float;

Si bien ambos métodos funcionan, es mejor utilizar el operador de flecha -> lugar de la combinación de paréntesis, el operador de referencia * y el punto . Operador porque es más fácil de leer y entender, especialmente con usos anidados.

Otra diferencia importante se muestra a continuación:

MY_STRUCT copy = *instance;
copy.my_int    = 2;

En este caso, la copy contiene una copia del contenido de la instance . Cambiar my_int de copy no lo cambiará en la instance .

MY_STRUCT *ref = instance;
ref->my_int    = 2;

En este caso, ref es una referencia a la instance . Cambiar my_int usando la referencia lo cambiará en la instance .

Es una práctica común utilizar punteros a estructuras como parámetros en funciones, en lugar de las estructuras en sí. Usar las estructuras como parámetros de función podría hacer que la pila se desborde si la estructura es grande. El uso de un puntero a una estructura solo utiliza suficiente espacio de pila para el puntero, pero puede causar efectos secundarios si la función cambia la estructura que se pasa a la función.

Punteros de funcion

Los punteros también se pueden utilizar para señalar funciones.

Tomemos una función básica:

int my_function(int a, int b)
{
    return 2 * a + 3 * b;
}

Ahora, definamos un puntero del tipo de esa función:

int (*my_pointer)(int, int);

Para crear una, solo usa esta plantilla:

return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)

Entonces debemos asignar este puntero a la función:

my_pointer = &my_function;

Este puntero ahora se puede utilizar para llamar a la función:

/* Calling the pointed function */
int result = (*my_pointer)(4, 2);

...

/* Using the function pointer as an argument to another function */
void another_function(int (*another_pointer)(int, int))
{
    int a = 4;
    int b = 2;
    int result = (*another_pointer)(a, b);

    printf("%d\n", result);
}

Aunque esta sintaxis parece más natural y coherente con los tipos básicos, los punteros de función de atribución y eliminación de referencias no requieren el uso de los operadores & y * . Así que el siguiente fragmento de código es igualmente válido:

/* Attribution without the & operator */
my_pointer = my_function;

/* Dereferencing without the * operator */
int result = my_pointer(4, 2);

Para aumentar la legibilidad de los punteros de función, se pueden usar typedefs.

typedef void (*Callback)(int a);

void some_function(Callback callback)
{
    int a = 4;
    callback(a);
}

Otro truco de legibilidad es que el estándar C permite simplificar el puntero de una función en argumentos como el anterior (pero no en la declaración de variables) a algo que parece un prototipo de función; por lo tanto, lo siguiente se puede usar de manera equivalente para las definiciones y declaraciones de funciones:

void some_function(void callback(int))
{
    int a = 4;
    callback(a);
}

Ver también

Punteros a funciones

Inicializando los punteros

La inicialización del puntero es una buena manera de evitar los punteros salvajes. La inicialización es simple y no es diferente de la inicialización de una variable.

#include <stddef.h>

int main()
{
    int *p1 = NULL; 
    char *p2 = NULL;
    float *p3 = NULL;

         /* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */

    ...
}    

En la mayoría de los sistemas operativos, el uso involuntario de un puntero que se ha inicializado a NULL a menudo hace que el programa se bloquee de inmediato, lo que facilita la identificación de la causa del problema. El uso de un puntero no inicializado a menudo puede causar errores difíciles de diagnosticar.

Precaución:

El resultado de la anulación de la referencia de un puntero NULL no está definido, por lo que no necesariamente causará un bloqueo incluso si ese es el comportamiento natural del sistema operativo en el que se está ejecutando el programa. Las optimizaciones del compilador pueden enmascarar el bloqueo, provocar que se produzca el bloqueo antes o después del punto en el código fuente en el que se produjo la desreferencia del puntero nulo, o causar que partes del código que contiene la desreferencia del puntero nulo se eliminen inesperadamente del programa. Las versiones de depuración no suelen mostrar estos comportamientos, pero esto no está garantizado por el estándar de idioma. También se permite otro comportamiento inesperado y / o indeseable.

Debido a que NULL nunca apunta a una variable, a la memoria asignada oa una función, es seguro usarlo como valor de guarda.

Precaución:

Normalmente NULL se define como (void *)0 . Pero esto no implica que la dirección de memoria asignada sea 0x0 . Para obtener más información, consulte C-faq para punteros NULL

Tenga en cuenta que también puede inicializar los punteros para que contengan valores distintos de NULL.

int i1;

int main()
{
   int *p1 = &i1;
   const char *p2 = "A constant string to point to";
   float *p3 = malloc(10 * sizeof(float));
}

Dirección de Operador (&)

Para cualquier objeto (es decir, variable, matriz, unión, estructura, puntero o función), se puede utilizar el operador de dirección única para acceder a la dirección de ese objeto.

Suponer que

int i = 1;              
int *p = NULL;

Entonces, una declaración p = &i; , copia la dirección de la variable i al puntero p .

Se expresa como p apunta a i .

printf("%d\n", *p); Imprime 1, que es el valor de i .

Aritmética de puntero

Por favor vea aquí: Aritmética de punteros

Void * punteros como argumentos y valores de retorno a las funciones estándar

K&R

void* es un tipo de captura de todos los punteros a los tipos de objetos. Un ejemplo de esto en uso es con la función malloc , que se declara como

void* malloc(size_t);

El tipo de retorno de puntero a vacío significa que es posible asignar el valor de retorno de malloc a un puntero a cualquier otro tipo de objeto:

int* vector = malloc(10 * sizeof *vector);

En general, se considera una buena práctica no lanzar explícitamente los valores dentro y fuera de los punteros de vacío. En el caso específico de malloc() esto se debe a que, con una conversión explícita, el compilador puede asumir, pero no advertir, un tipo de retorno incorrecto para malloc() , si olvida incluir stdlib.h . También se trata de utilizar el comportamiento correcto de los punteros de vacío para ajustarse mejor al principio DRY (no se repita); compare lo anterior con lo siguiente, en donde el siguiente código contiene varios lugares adicionales innecesarios donde un error tipográfico podría causar problemas:

int* vector = (int*)malloc(10 * sizeof int*);

Del mismo modo, funciones como

void* memcpy(void *restrict target, void const *restrict source, size_t size);

sus argumentos se especifican como void * porque la dirección de cualquier objeto, independientemente del tipo, se puede pasar. Aquí también, una llamada no debe usar una conversión

unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);

Punteros const

Punteros individuales

  • Puntero a un int

    El puntero puede apuntar a diferentes enteros y los int pueden cambiarse a través del puntero. Esta muestra de código asigna b para apuntar a int b luego cambia el valor de b a 100 .

    int b;
    int* p;
    p = &b;    /* OK */
    *p = 100;  /* OK */
    
  • Puntero a una const int

    El puntero puede apuntar a diferentes enteros, pero el valor del int no se puede cambiar a través del puntero.

    int b;
    const int* p;
    p = &b;    /* OK */
    *p = 100;  /* Compiler Error */
    
  • puntero de const a int

    El puntero solo puede apuntar a un int pero el valor del int puede cambiarse a través del puntero.

    int a, b;
    int* const p = &b; /* OK as initialisation, no assignment */
    *p = 100;  /* OK */
    p = &a;    /* Compiler Error */
    
  • const puntero a const int

    El puntero solo puede apuntar a un int y el int no se puede cambiar a través del puntero.

    int a, b;
    const int* const p = &b; /* OK as initialisation, no assignment */
    p = &a;   /* Compiler Error */
    *p = 100; /* Compiler Error */
    

Puntero a puntero

  • Puntero a un puntero a un int

    Este código asigna la dirección de p1 al puntero doble p (que luego apunta a int* p1 (que apunta a int )).

    Luego cambia p1 para apuntar a int a . Luego cambia el valor de a para ser 100.

    void f1(void)
    {
      int a, b;
      int *p1;
      int **p;
      p1 = &b; /* OK */
      p = &p1; /* OK */
      *p = &a; /* OK */
      **p = 100; /* OK */
    }
    
  • Puntero a puntero a una const int

     void f2(void)
    {
      int b;
      const int *p1;
      const int **p;
      p = &p1; /* OK */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • Puntero a const puntero a un int

    void f3(void)
    {
      int b;
      int *p1;
      int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    
  • const puntero a puntero a int

    void f4(void)
    {
      int b;
      int *p1;
      int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* OK */
    }
    
  • Puntero a const puntero a const int

    void f5(void)
    {
      int b;
      const int *p1;
      const int * const *p;
      p = &p1; /* OK */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const puntero a puntero a const int

    void f6(void)
    {
      int b;
      const int *p1;
      const int ** const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’ */
      *p = &b; /* OK */
      **p = 100; /* error: assignment of read-only location ‘**p’ */
    }
    
  • const point to const point to int

    void f7(void)
    {
      int b;
      int *p1;
      int * const * const p = &p1; /* OK as initialisation, not assignment */
      p = &p1; /* error: assignment of read-only variable ‘p’  */
      *p = &b; /* error: assignment of read-only location ‘*p’ */
      **p = 100; /* OK */
    }
    

Mismo Asterisco, Diferentes Significados

Premisa

Lo más confuso que rodea la sintaxis del puntero en C y C ++ es que en realidad hay dos significados diferentes que se aplican cuando el símbolo del puntero, el asterisco ( * ), se usa con una variable.

Ejemplo

En primer lugar, utiliza * para declarar una variable de puntero.

int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */

Cuando no está declarando (o multiplicando), * se usa para eliminar la referencia de una variable de puntero:

*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */

Cuando quieres que una variable de puntero existente contenga la dirección de otra variable, no usas * , pero hazlo así:

p = &another_variable;

Una confusión común entre los novatos en la programación C surge cuando declaran e inicializan una variable de puntero al mismo tiempo.

int *p = &i;

Dado que int i = 5; e int i; i = 5; dar el mismo resultado, algunos de ellos podrían pensar int *p = &i; e int *p; *p = &i; Da el mismo resultado también. El hecho es, no, int *p; *p = &i; intentará deferir un puntero no inicializado que dará como resultado UB. Nunca use * cuando no esté declarando ni desreferir un puntero.

Conclusión

El asterisco ( * ) tiene dos significados distintos dentro de C en relación con los punteros, dependiendo de dónde se use. Cuando se utiliza dentro de una declaración de variable , el valor en el lado derecho del lado igual debe ser un valor de puntero a una dirección en la memoria. Cuando se usa con una variable ya declarada , el asterisco eliminará la referencia del valor del puntero, lo seguirá hasta el lugar señalado en la memoria y permitirá que el valor almacenado allí sea asignado o recuperado.

Para llevar

Es importante tener en cuenta sus P y Q, por así decirlo, al tratar con punteros. Tenga en cuenta cuándo está usando el asterisco y qué significa cuando lo usa allí. Pasar por alto este pequeño detalle podría resultar en un comportamiento defectuoso y / o indefinido con el que realmente no quieres tener que lidiar.

Puntero a puntero

En C, un puntero puede referirse a otro puntero.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &pA;
  int*** pppA = &ppA;

  printf("%d", ***pppA); /* prints 42 */

  return EXIT_SUCCESS;
}

Pero, referencia y referencia directamente no está permitida.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
  int A = 42;
  int* pA = &A;
  int** ppA = &&A; /* Compilation error here! */
  int*** pppA = &&&A;  /* Compilation error here! */

  ...

Introducción

Un puntero se declara de forma muy similar a cualquier otra variable, excepto que se coloca un asterisco ( * ) entre el tipo y el nombre de la variable para denotar que es un puntero.

int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */

Para declarar dos variables de puntero del mismo tipo, en la misma declaración, use el símbolo de asterisco antes de cada identificador. Por ejemplo,

int *iptr1, *iptr2;
int *iptr3,  iptr4;  /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */

El operador de dirección o referencia denotado por un signo ( & ) da la dirección de una variable dada que se puede colocar en un puntero del tipo apropiado.

int value = 1;
pointer = &value;

El operador de direccionamiento indirecto o de desreferencia indicado por un asterisco ( * ) obtiene el contenido de un objeto apuntado por un puntero.

printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */

Si el puntero apunta a una estructura o tipo de unión, puede anular la referencia y acceder a sus miembros directamente mediante el operador -> :

SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */

En C, un puntero es un tipo de valor distinto que se puede reasignar y, de lo contrario, se trata como una variable por derecho propio. Por ejemplo, el siguiente ejemplo imprime el valor del puntero (variable).

printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */

Debido a que un puntero es una variable mutable, es posible que no apunte a un objeto válido, ya sea que se establezca en nulo

pointer = 0;     /* or alternatively */
pointer = NULL;

o simplemente al contener un patrón de bits arbitrario que no es una dirección válida. La última es una situación muy mala, ya que no se puede probar antes de que se elimine la referencia al puntero, solo hay una prueba para el caso de que el puntero sea nulo:

if (!pointer) exit(EXIT_FAILURE);

Un puntero solo puede ser referenciado si apunta a un objeto válido , de lo contrario el comportamiento es indefinido. Muchas implementaciones modernas pueden ayudarlo al generar algún tipo de error, como un error de segmentación y terminar la ejecución, pero otras pueden dejar su programa en un estado no válido.

El valor devuelto por el operador de referencia es un alias mutable a la variable original, por lo que se puede cambiar, modificando la variable original.

*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */

Los punteros también son re-asignables. Esto significa que un puntero que apunta a un objeto se puede usar más tarde para apuntar a otro objeto del mismo tipo.

int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */

Como cualquier otra variable, los punteros tienen un tipo específico. No puede asignar la dirección de un short int a un puntero a un long int , por ejemplo. Este tipo de comportamiento se conoce como punning de tipo y está prohibido en C, aunque hay algunas excepciones.

Aunque el puntero debe ser de un tipo específico, la memoria asignada para cada tipo de puntero es igual a la memoria utilizada por el entorno para almacenar direcciones, en lugar del tamaño del tipo al que se apunta.

#include <stdio.h>

int main(void) {
    printf("Size of int pointer: %zu\n", sizeof (int*));      /* size 4 bytes */
    printf("Size of int variable: %zu\n", sizeof (int));      /* size 4 bytes */
    printf("Size of char pointer: %zu\n", sizeof (char*));    /* size 4 bytes */
    printf("Size of char variable: %zu\n", sizeof (char));    /* size 1 bytes */
    printf("Size of short pointer: %zu\n", sizeof (short*));  /* size 4 bytes */
    printf("Size of short variable: %zu\n", sizeof (short));  /* size 2 bytes */
    return 0;
}

(NB: si está utilizando Microsoft Visual Studio, que no es compatible con los estándares C99 o C11, debe usar %Iu 1 en lugar de %zu en el ejemplo anterior).

Tenga en cuenta que los resultados anteriores pueden variar de un entorno a otro en números, pero todos los entornos mostrarían los mismos tamaños para diferentes tipos de punteros.

Extracto basado en información de Cardiff University C Punteros Introducción

Punteros y matrices

Los punteros y las matrices están íntimamente conectados en C. Las matrices en C siempre se guardan en ubicaciones contiguas en la memoria. La aritmética de punteros siempre se escala según el tamaño del elemento apuntado. Entonces, si tenemos una matriz de tres dobles y un puntero a la base, *ptr refiere al primer doble, *(ptr + 1) al segundo, *(ptr + 2) al tercero. Una notación más conveniente es usar la notación de matriz [] .

double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;

/* prints x 0.0, y 1.0 z 2.0 */ 
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);

Así que esencialmente ptr y el nombre de la matriz son intercambiables. Esta regla también significa que una matriz se desintegra en un puntero cuando se pasa a una subrutina.

double point[3] = {0.0, 1.0, 2.0};

printf("length of point is %s\n", length(point));

/* get the distance of a 3D point from the origin */ 
double length(double *pt)
{
   return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}

Un puntero puede apuntar a cualquier elemento en una matriz, o al elemento más allá del último elemento. Sin embargo, es un error establecer un puntero a cualquier otro valor, incluido el elemento antes de la matriz. (La razón es que en las arquitecturas segmentadas la dirección antes del primer elemento puede cruzar un límite de segmento, el compilador garantiza que no suceda para el último elemento más uno).


Nota al pie 1: la información de formato de Microsoft se puede encontrar a través de printf() y la sintaxis de especificación de formato .

Comportamiento polimórfico con punteros vacíos.

La función de biblioteca estándar qsort() es un buen ejemplo de cómo se pueden usar los punteros de vacío para que una sola función funcione en una gran variedad de tipos diferentes.

void qsort (
    void *base,                                 /* Array to be sorted */
    size_t num,                                 /* Number of elements in array */
    size_t size,                                /* Size in bytes of each element */
    int (*compar)(const void *, const void *)); /* Comparison function for two elements */

La matriz que se va a ordenar se pasa como un puntero de vacío, por lo que se puede utilizar una matriz de cualquier tipo de elemento. Los siguientes dos argumentos le dicen a qsort() cuántos elementos debería esperar en la matriz, y qué tan grande, en bytes, es cada elemento.

El último argumento es un puntero de función a una función de comparación que a su vez toma dos punteros nulos. Al hacer que la persona que llama proporcione esta función, qsort() puede ordenar efectivamente los elementos de cualquier tipo.

Aquí hay un ejemplo de tal función de comparación, para comparar flotadores. Tenga en cuenta que cualquier función de comparación pasada a qsort() debe tener este tipo de firma. La forma en que se hace polimórfico es mediante la conversión de los argumentos de puntero nulo a los indicadores del tipo de elemento que deseamos comparar.

int compare_floats(const void *a, const void *b)
{
    float fa = *((float *)a);
    float fb = *((float *)b);
    if (fa < fb)
        return -1;
    if (fa > fb)
        return 1;
    return 0;
}

Como sabemos que qsort utilizará esta función para comparar flotantes, devolvemos los argumentos de los punteros nulos a los punteros flotantes antes de eliminarlas.

Ahora, el uso de la función polimórfica qsort en una matriz "array" con longitud "len" es muy simple:

qsort(array, len, sizeof(array[0]), compare_floats);


Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow