tutorial smart programar para introduccion inteligentes inteligente ejemplos crear contratos contrato c++ pointers c++11 pointer-arithmetic

c++ - smart - Forma portátil y segura de agregar un byte offset a cualquier puntero.



programar contratos inteligentes (4)

Soy bastante nuevo en el trabajo con C ++ y no he comprendido todas las complejidades y sutilezas del lenguaje.

¿Cuál es la forma más portátil, correcta y segura de agregar un desplazamiento de byte arbitrario a un puntero de cualquier tipo en C ++ 11?

SomeType* ptr; int offset = 12345 /* bytes */; ptr = ptr + offset; // <--

Encontré muchas respuestas en Stack Overflow y Google, pero todas proponen cosas diferentes. Algunas variantes que he encontrado:

  1. Cast to char * :

    ptr = (SomeType*)(((char*)ptr) + offset);

  2. Cast a unsigned int :

    ptr = (SomeType*)((unsigned int)ptr) + offset);

  3. Convertir a size_t :

    ptr = (SomeType*)((size_t)ptr) + offset);

  4. "El tamaño de size_t y ptrdiff_t siempre coincide con el tamaño del puntero. Debido a esto, son estos tipos los que deben usarse como índices para matrices grandes, para el almacenamiento de punteros y la aritmética de punteros". - Acerca de size_t y ptrdiff_t en CodeProject

    ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);

  5. O como el anterior, pero con intptr_t lugar de size_t , que está firmado en lugar de unsigned:

    ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);

  6. Solo se intptr_t a intptr_t , ya que offset ya es un entero con signo e intptr_t no es size_t :

    ptr = (SomeType*)((intptr_t)ptr) + offset);

Y en todos estos casos, ¿es seguro usar moldes antiguos de estilo C, o es más seguro o más portátil usar static_cast o reinterpret_cast para esto?

¿Debo suponer que el valor del puntero en sí no está firmado o firmado?


El uso de reinterpret_cast (o conversión de estilo C) significa eludir el tipo de sistema y no es portátil y no es seguro. Si es correcto, depende de tu arquitectura. Si usted (debe) hacerlo, insinúa que sabe lo que hace y básicamente está solo desde ese momento. Tanto para la advertencia.

Si agrega un número n a un puntero o escribe T , mueve este puntero por n elementos de tipo T Lo que está buscando es un tipo donde 1 elemento significa 1 byte.

De la sección de tamaño 5.3.3.1 .:

El operador sizeof produce el número de bytes en la representación del objeto de su operando. [...] sizeof(char) , sizeof(signed char) y sizeof(unsigned char) son 1 . El resultado de sizeof aplicado a cualquier otro tipo fundamental (3.9.1) está definido por la implementación.

Tenga en cuenta que no hay ninguna declaración sobre sizeof(int) , etc.

Definición de byte (sección 1.7.1.):

La unidad de almacenamiento fundamental en el modelo de memoria C ++ es el byte. Un byte es al menos lo suficientemente grande como para contener cualquier miembro del conjunto de caracteres de ejecución básico (2.3) y las unidades de código de ocho bits de la forma de codificación Unicode UTF-8 y está compuesto por una secuencia contigua de bits, cuyo número es Implementación definida. [...] La memoria disponible para un programa C ++ consiste en una o más secuencias de bytes contiguos. Cada byte tiene una dirección única.

Por lo tanto, si sizeof devuelve el número de bytes y sizeof(char) es 1, entonces char tiene el tamaño de un byte a C ++. Por lo tanto, char es lógicamente un byte a C ++ pero no necesariamente el byte estándar de facto de 8 bits. Agregar n a un char* devolverá un puntero que está n bytes (en términos del modelo de memoria C ++). Por lo tanto, si quieres jugar el peligroso juego de manipular el puntero de un objeto en el sentido de un byte, debes lanzarlo a una de las variantes char . Si su tipo también tiene calificadores como const , también debería transferirlos a su "tipo de byte".

template <typename Dst, typename Src> struct adopt_const { using type = typename std::conditional< std::is_const<Src>::value, typename std::add_const<Dst>::type, Dst>::type; }; template <typename Dst, typename Src> struct adopt_volatile { using type = typename std::conditional< std::is_volatile<Src>::value, typename std::add_volatile<Dst>::type, Dst>::type; }; template <typename Dst, typename Src> struct adopt_cv { using type = typename adopt_const< typename adopt_volatile<Dst, Src>::type, Src>::type; }; template <typename T> T* add_offset(T* p, std::ptrdiff_t delta) noexcept { using byte_type = typename adopt_cv<unsigned char, T>::type; return reinterpret_cast<T*>(reinterpret_cast<byte_type*>(p) + delta); }

Example


Tenga en cuenta que, NULL es especial. Añadir un offset en él es peligroso.
reinterpret_cast no puede eliminar const o calificadores volatile . Forma más portátil es el reparto de estilo C
reinterpret_cast con rasgos como la respuesta de @ user2218982, parece más seguro.

template <typename T> inline void addOffset( std::ptrdiff_t offset, T *&ptr ) { if ( !ptr ) return; ptr = (T*)( (unsigned char*)ptr + offset ); }


Yo usaría algo como:

unsigned char* bytePtr = reinterpret_cast<unsigned char*>(ptr); bytePtr += offset;


si usted tiene:

myType *ptr;

y lo hace:

ptr+=3;

El compilador sin duda incrementará tu variable por:

3*sizeof(myType)

Y es la forma estándar de hacerlo hasta donde sé.

Si quiere iterar una vez más, digamos una serie de elementos de tipo myType que es la forma de hacerlo.

Ok, si quieres lanzar haz eso usando

myNewType *newPtr=reinterpret_cast < myNewType * > ( ptr )

O apégate a la vieja C y haz:

myNewType *newPtr=(myNewType *) ptr;

Y luego incrementar