¿Por qué aumentar los punteros?

25

Recientemente comencé a aprender C ++, y como la mayoría de las personas (según lo que he estado leyendo) estoy luchando con los punteros.

No en el sentido tradicional, entiendo lo que son, y por qué se usan, y cómo pueden ser útiles, sin embargo, no puedo entender cómo sería útil incrementar los punteros, ¿alguien puede dar una explicación de cómo incrementar un puntero es un concepto útil y idiomático C ++?

Esta pregunta surgió después de que comencé a leer el libro Un recorrido por C ++ de Bjarne Stroustrup, me recomendaron este libro, porque estoy bastante familiarizado con Java, y los chicos de Reddit me dijeron que sería un buen libro de 'cambio' .

INdek
fuente
11
Un puntero es solo un iterador
Charles Salvia
1
Es una de las herramientas favoritas para escribir virus informáticos que leen lo que no deberían leer. También es uno de los casos más comunes de vulnerabilidad en las aplicaciones (cuando uno incrementa un puntero más allá del área donde se supone que debe hacerlo, luego lo lee o lo escribe)> Vea el error HeartBleed.
Sam
1
@vasile Esto es lo malo de los punteros.
Cruncher el
44
Lo bueno / malo de C ++ es que te permite hacer mucho más antes de llamar a un segfault. Por lo general, obtienes un defecto de seguridad cuando intentas acceder a la memoria de otro proceso, la memoria del sistema o la memoria protegida de la aplicación. El sistema permite cualquier acceso dentro de las páginas de aplicación habituales, y depende del programador / compilador / lenguaje imponer límites razonables. C ++ prácticamente te permite hacer lo que quieras. En cuanto a openssl que tiene su propio administrador de memoria, eso no es cierto. Solo tiene los mecanismos de acceso de memoria C ++ predeterminados.
Sam
1
@INdek: solo obtendrá un segfault si la memoria a la que intenta acceder está protegida. La mayoría de los sistemas operativos asignan protección a nivel de página, por lo que generalmente puede acceder a cualquier cosa que esté en la página en la que se inicia su puntero. Si el sistema operativo utiliza un tamaño de página de 4K, es una buena cantidad de datos. Si su puntero comienza en algún lugar del montón, nadie sabe a cuántos datos puede acceder.
TMN

Respuestas:

46

Cuando tiene una matriz, puede configurar un puntero para apuntar a un elemento de la matriz:

int a[10];
int *p = &a[0];

Aquí papunta al primer elemento de a, que es a[0]. Ahora puede incrementar el puntero para señalar el siguiente elemento:

p++;

Ahora papunta al segundo elemento, a[1]. Puede acceder al elemento aquí usando *p. Esto es diferente de Java, donde tendría que usar una variable de índice entero para acceder a elementos de una matriz.

Incrementar un puntero en C ++ donde ese puntero no apunta a un elemento de una matriz es un comportamiento indefinido .

Greg Hewgill
fuente
23
Sí, con C ++ usted es responsable de evitar errores de programación como el acceso fuera de los límites de una matriz.
Greg Hewgill
99
No, incrementar un puntero que apunta a cualquier cosa, excepto a un elemento de matriz, es un comportamiento indefinido. Sin embargo, si está haciendo algo de bajo nivel y no portátil, entonces incrementar un puntero generalmente no es más que acceder a lo siguiente en la memoria, sea lo que sea.
Greg Hewgill
44
Hay algunas cosas que son o pueden tratarse como una matriz; una cadena de texto es, de hecho, una matriz de caracteres. En algunos casos, un int largo se trata como una matriz de bytes, aunque esto puede meterte fácilmente en problemas.
AMADANON Inc.
66
Eso le indica el tipo , pero el comportamiento se describe en 5.7 Operadores aditivos [expr.add]. Específicamente, 5.7 / 5 dice que ir a cualquier parte fuera de la matriz, excepto una pasada es UB.
Inútil el
44
El último párrafo es: si tanto el operando del puntero como el resultado apuntan a elementos del mismo objeto de matriz, la evaluación no producirá un desbordamiento; de lo contrario, el comportamiento es indefinido . Entonces, si el resultado no está en la matriz ni una vez pasado el final, obtienes UB.
Inútil el
37

El aumento de punteros es idiomático C ++, porque la semántica de puntero refleja un aspecto fundamental de la filosofía de diseño detrás de la biblioteca estándar C ++ (basada en STL de Alexander Stepanov )

El concepto importante aquí es que el STL está diseñado alrededor de contenedores, algoritmos e iteradores. Los punteros son simplemente iteradores .

Por supuesto, la capacidad de incrementar (o sumar / restar) punteros se remonta a C. Se pueden escribir muchos algoritmos de manipulación de cadenas en C simplemente usando la aritmética de punteros. Considere el siguiente código:

char string1[4] = "abc";
char string2[4];
char* src = string1;
char* dest = string2;
while ((*dest++ = *src++));

Este código usa aritmética de puntero para copiar una cadena en C terminada en nulo. El ciclo termina automáticamente cuando encuentra el nulo.

Con C ++, la semántica de puntero se generaliza al concepto de iteradores . La mayoría de los contenedores estándar de C ++ proporcionan iteradores, a los que se puede acceder a través de las funciones beginy endmiembro. Los iteradores se comportan como punteros, en el sentido de que pueden incrementarse, desreferenciarse y, a veces, disminuirse o avanzarse.

Para iterar sobre un std::string, diríamos:

std::string s = "abcdef";
std::string::iterator it = s.begin();
for (; it != s.end(); ++it) std::cout << *it;

Incrementamos el iterador al igual que incrementaríamos un puntero a una cadena en C simple. La razón por la que este concepto es poderoso es porque puede usar plantillas para escribir funciones que funcionarán para cualquier tipo de iterador que cumpla con los requisitos de concepto necesarios. Y este es el poder de la STL:

std::string s1 = "abcdef";
std::vector<char> buf;
std::copy(s1.begin(), s1.end(), std::back_inserter(buf));

Este código copia una cadena en un vector. La copyfunción es una plantilla que funcionará con cualquier iterador que admita el incremento (que incluye punteros simples). Podríamos usar la misma copyfunción en una cadena en C simple:

   const char* s1 = "abcdef";
   std::vector<char> buf;
   std::copy(s1, s1 + std::strlen(s1), std::back_inserter(buf));

Podríamos usar copyen uno std::mapo uno std::seto cualquier contenedor personalizado que admita iteradores.

Tenga en cuenta que los punteros son un tipo específico de iterador: iterador de acceso aleatorio , lo que significa que admiten el incremento, la disminución y el avance con el operador +y -. Otros tipos de iteradores solo admiten un subconjunto de semántica de puntero: un iterador bidireccional admite al menos incrementos y decrementos; un iterador directo admite al menos incrementos. (Todos los tipos de iteradores admiten la desreferenciación). La copyfunción requiere un iterador que al menos admita el incremento.

Puede leer sobre diferentes conceptos de iterador aquí .

Por lo tanto, incrementar los punteros es una forma idiomática de C ++ de iterar sobre una matriz C, o acceder a elementos / compensaciones en una matriz C.

Charles Salvia
fuente
3
Aunque uso punteros como en el primer ejemplo, nunca pensé en ello como un iterador, ahora tiene mucho sentido.
dyesdyes
1
"El bucle termina automáticamente cuando encuentra el nulo". Este es un idioma aterrador.
Charles Wood
99
@CharlesWood, entonces supongo que debes encontrar a C bastante aterrador
Siler
77
@CharlesWood: la alternativa es utilizar la longitud de la cadena como la variable de control de bucle, lo que significa atravesar la cadena dos veces (una para determinar la longitud y otra para copiar los caracteres). Cuando se ejecuta en un PDP-7 de 1MHz, eso realmente puede comenzar a acumularse.
TMN
3
@INdek: en primer lugar, C y C ++ intentan evitar a toda costa introducir cambios importantes, y diría que alterar el comportamiento predeterminado de los literales de cadena sería una gran modificación. Pero lo más importante, las cadenas terminadas en cero son solo una convención (se hace fácil de seguir por el hecho de que los literales de cadena están terminados en cero de manera predeterminada y que las funciones de la biblioteca los esperan), nadie impide que use cadenas contadas en C, en realidad, varias bibliotecas C las usan (ver, por ejemplo, BSTR de OLE).
Matteo Italia
16

La aritmética del puntero está en C ++ porque estaba en C. La aritmética del puntero está en C porque es un modismo normal en ensamblador .

Hay muchos sistemas en los que el "registro de incremento" es más rápido que "cargar el valor constante 1 y agregar al registro". Además, bastantes sistemas le permiten "cargar DWORD en A desde la dirección especificada en el registro B, y luego agregar sizeof (DWORD) a B" en una sola instrucción. En estos días puede esperar que un compilador optimizador solucione esto por usted, pero esta no era realmente una opción en 1973.

Esta es básicamente la misma razón por la que las matrices C no tienen límites de verificación y las cadenas C no tienen un tamaño incrustado en ellas: el lenguaje se desarrolló en un sistema donde cada byte y cada instrucción contaban.

pjc50
fuente