Aritmética de punteros [closed]

9 minutos de lectura

avatar de usuario
leora

¿Alguien tiene buenos artículos o explicaciones (blogs, ejemplos) para la aritmética de punteros? Imagínese que la audiencia es un grupo de programadores de Java que están aprendiendo C y C++.

  • c-faq.com Sección 4

    –Keith Thompson

    13/10/2015 a las 20:29

avatar de usuario
Johannes Schaub – litb

Primero el chupete el vídeo puede ayudar. Es un buen video sobre punteros. Para la aritmética, aquí hay un ejemplo:

int * pa = NULL;
int * pb = NULL;
pa += 1; // pa++. behind the scenes, add sizeof(int) bytes
assert((pa - pb) == 1);

print_out(pa); // possibly outputs 0x4
print_out(pb); // possibly outputs 0x0 (if NULL is actually bit-wise 0x0)

(Tenga en cuenta que incrementar un puntero que contiene un valor de puntero nulo es estrictamente un comportamiento indefinido. Usamos NULL porque solo estábamos interesados ​​en el valor del puntero. Normalmente, solo use incremento/decremento cuando apunte a elementos de una matriz).

A continuación se muestran dos conceptos importantes

  • suma/resta de un número entero a un puntero significa mover el puntero hacia adelante/atrás por N elementos. Entonces, si un int tiene un tamaño de 4 bytes, pa podría contener 0x4 en nuestra plataforma después de haberlo incrementado en 1.
  • La resta de un puntero por otro puntero significa obtener su distancia, medida por elementos. Entonces, restar pb de pa dará como resultado 1, ya que tienen una distancia de un elemento.

En un ejemplo práctico. Suponga que escribe una función y la gente le proporciona un puntero de inicio y fin (algo muy común en C++):

void mutate_them(int *begin, int *end) {
    // get the amount of elements
    ptrdiff_t n = end - begin;
    // allocate space for n elements to do something...
    // then iterate. increment begin until it hits end
    while(begin != end) {
        // do something
        begin++;
    }
}

ptrdiff_t es lo que es el tipo de (fin – comienzo). Puede ser un sinónimo de “int” para algún compilador, pero puede ser otro tipo para otro. Uno no puede saber, por lo que elige el typedef genérico ptrdiff_t.

  • Cabe señalar que o define ptrdiff_t (según el estándar). No es un tipo especial, diría yo, solo un nombre (typedef) para el tipo que escupe el compilador.

    – extraño

    27 de diciembre de 2008 a las 7:46

  • sí, de hecho, debe ser algún tipo de entero con signo. no quería entrar demasiado en detalles.

    – Johannes Schaub – litb

    27 de diciembre de 2008 a las 7:50

  • Odio señalarlo, pero su primer ejemplo es un comportamiento indefinido. 😉 No está permitido incrementar un puntero nulo. 🙂

    – jalf

    27 de diciembre de 2008 a las 9:52

  • jalf, gracias por señalarlo. agregué una nota al respecto 🙂 bueno, ahora mi dicho “si NULL es en realidad 0 bit a bit” está roto, ya que 0 es un número entero cero. pero creo que sabes a lo que me refiero jeje

    – Johannes Schaub – litb

    27 de diciembre de 2008 a las 10:41

  • En general, me resulta difícil seguir los tutoriales en video, pero este divertido fue súper simple, increíble y divertido :). Clava lo básico en solo 3 minutos.

    – RBT

    13 de septiembre de 2016 a las 2:53

  • No estoy de acuerdo con el ‘siguiente’. No está claro si el siguiente puntero está a 4b de distancia o si se refiere al siguiente 4b al que se puede apuntar (reutilizando el 3b inferior anterior). Esto rara vez se usa, claro, pero la analogía dada aquí no funciona para mí.

    – mafu

    30 de enero de 2018 a las 4:04

  • Esta es en realidad una respuesta muy clara. Me gusta mucho la parte que explica visualmente que “*pag” es un número entero.

    – j3141592653589793238

    1 de agosto de 2020 a las 16:06

Hay varias formas de abordarlo.

El enfoque intuitivo, que es lo que piensan la mayoría de los programadores de C/C++, es que los punteros son direcciones de memoria. El ejemplo de litb adopta este enfoque. Si tiene un puntero nulo (que en la mayoría de las máquinas corresponde a la dirección 0) y agrega el tamaño de un int, obtiene la dirección 4. Esto implica que los punteros son básicamente números enteros elegantes.

Desafortunadamente, hay algunos problemas con esto. Para empezar, puede que no funcione. No se garantiza que un puntero nulo use realmente la dirección 0. (Aunque la asignación de la constante 0 a un puntero produce el puntero nulo).

Además, no se le permite incrementar el puntero nulo, o más generalmente, un puntero siempre debe apuntar a la memoria asignada (o un elemento pasado), o la constante especial de puntero nulo 0.

Entonces, una forma más correcta de pensarlo es que los punteros son simplemente iteradores que le permiten iterar sobre la memoria asignada. Esta es realmente una de las ideas clave detrás de los iteradores STL. Están modelados para comportarse como punteros y para proporcionar especializaciones que parchean los punteros sin procesar para que funcionen como iteradores adecuados.

Una explicación más elaborada de esto se da aquípor ejemplo.

Pero este último punto de vista significa que realmente debería explicar los iteradores STL y luego simplemente decir que los punteros son un caso especial de estos. Puede incrementar un puntero para apuntar al siguiente elemento en el búfer, al igual que puede std::vector<int>::iterator. Puede apuntar un elemento más allá del final de una matriz, al igual que el iterador final en cualquier otro contenedor. Puedes restar dos punteros ese punto en el mismo búfer para obtener la cantidad de elementos entre ellos, al igual que con los iteradores, y al igual que con los iteradores, si los punteros apuntan a búferes separados, puede no compararlos significativamente. (Para un ejemplo práctico de por qué no, considere lo que sucede en un espacio de memoria segmentado. ¿Cuál es la distancia entre dos punteros que apuntan a segmentos separados?)

Por supuesto, en la práctica, existe una correlación muy estrecha entre las direcciones de la CPU y los punteros de C/C++. pero no lo son exactamente la misma cosa. Los punteros tienen algunas limitaciones que pueden no ser estrictamente necesarias en su CPU.

Por supuesto, la mayoría de los programadores de C++ se confunden con la primera comprensión, aunque sea técnicamente incorrecto. Por lo general, está lo suficientemente cerca de cómo termina comportándose su código para que las personas piensen que lo entienden y siguen adelante.

Pero para alguien que viene de Java, y que acaba de aprender sobre punteros desde cero, la última explicación puede ser igual de fácil de entender, y les traerá menos sorpresas más adelante.

  • Es importante tener en cuenta que un puntero no siempre tiene una longitud de 4 bytes. En algunos sistemas (64 bits) puede tener una longitud de 8 bytes. Por ejemplo, nunca suponga que un puntero tiene el mismo tamaño que un número entero. ¡Se han cometido demasiados errores porque los programadores ocultan punteros en números enteros!

    – cabeza de cera

    27 de diciembre de 2013 a las 1:44

  • Cierto, ese es un punto importante. Tenía la intención de que los 4 bytes fueran solo un ejemplo, pero probablemente debería haberlo dejado más claro, porque los punteros ciertamente no son garantizado ser de 4 bytes, y a menudo no lo son.

    – jalf

    27 de diciembre de 2013 a las 12:37

avatar de usuario
arúl

Considero un buen ejemplo de aritmética de punteros la siguiente función de longitud de cadena:

int length(char *s)
{
   char *str = s;
   while(*str++);
   return str - s;
}

  • Es importante tener en cuenta que un puntero no siempre tiene una longitud de 4 bytes. En algunos sistemas (64 bits) puede tener una longitud de 8 bytes. Por ejemplo, nunca suponga que un puntero tiene el mismo tamaño que un número entero. ¡Se han cometido demasiados errores porque los programadores ocultan punteros en números enteros!

    – cabeza de cera

    27 de diciembre de 2013 a las 1:44

  • Cierto, ese es un punto importante. Tenía la intención de que los 4 bytes fueran solo un ejemplo, pero probablemente debería haberlo dejado más claro, porque los punteros ciertamente no son garantizado ser de 4 bytes, y a menudo no lo son.

    – jalf

    27 de diciembre de 2013 a las 12:37

Entonces, la clave para recordar es que un puntero es solo una variable del tamaño de una palabra que se escribe para eliminar la referencia. Eso significa que ya sea un void *, int *, long long **, sigue siendo solo una variable del tamaño de una palabra. La diferencia entre estos tipos es lo que el compilador considera el tipo desreferenciado. Solo para aclarar, el tamaño de la palabra significa el ancho de una dirección virtual. Si no sabe lo que esto significa, recuerde que en una máquina de 64 bits, los punteros son de 8 bytes, y en una máquina de 32 bits, los punteros son de 4 bytes. El concepto de una dirección es SÚPER importante para comprender los punteros. Una dirección es un número capaz de identificar de forma única una determinada ubicación en la memoria. Todo en la memoria tiene una dirección. Para nuestros propósitos, podemos decir que cada variable tiene una dirección. Esto no siempre es necesariamente cierto, pero el compilador nos permite asumirlo. La dirección en sí es granular en bytes, lo que significa que 0x0000000 especifica el comienzo de la memoria y 0x00000001 es un byte en la memoria. Esto significa que al agregar uno a un puntero, estamos avanzando un byte en la memoria. Ahora, tomemos matrices. Si crea una matriz de tipo quux que tiene 32 elementos de tamaño, se extenderá desde el comienzo de su asignación hasta el comienzo de su asignación más 32 * sizeof (quux), ya que cada celda de la matriz es sizeof (quux) grande. Entonces, realmente cuando especificamos un elemento de una matriz con matriz[n], eso es solo azúcar sintáctico (abreviatura) para *(array+sizeof(quux)*n). La aritmética de punteros en realidad solo cambia la dirección a la que te refieres, por lo que podemos implementar strlen con

while(*n++ != '\0'){
  len++;
}

ya que solo estamos escaneando, byte por byte hasta que lleguemos a cero. ¡Espero que ayude!

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad