Переносимый и безопасный способ добавления байтового смещения к любому указателю

Я довольно новичок в работе с C ++ и не осознал всех тонкостей языка.

Какой самый портативный, правильный и безопасный способ добавить произвольное смещение байта для указателя любого типа в C ++ 11?

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

Я нашел много ответов на Stack Overflow и Google, но все они предлагают разные вещи. Некоторые варианты, с которыми я столкнулся:

  1. Приведение к char *:

    ptr = (SomeType*)(((char*)ptr) + offset);
    
  2. Приведение к unsigned int:

    ptr = (SomeType*)((unsigned int)ptr) + offset);
    
  3. Приведение к size_t:

    ptr = (SomeType*)((size_t)ptr) + offset);
    
  4. «Размер size_t а также ptrdiff_t всегда совпадают с размером указателя. Из-за этого именно эти типы должны использоваться в качестве индексов для больших массивов, для хранения указателей и арифметики указателей. «- О size_t и ptrdiff_t на CodeProject

    ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
    
  5. Или как предыдущий, но с intptr_t вместо size_t, который подписан вместо неподписанного:

    ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
    
  6. Только в ролях intptr_t, поскольку offset это уже целое число со знаком и intptr_t не является size_t:

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

И во всех этих случаях безопасно ли использовать старые приведения в стиле C, или это безопаснее или более портативно для использования static_cast или же reinterpret_cast за это?

Должен ли я считать, что само значение указателя не подписано или подписано?

17

Решение

Я бы использовал что-то вроде:

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

Другие решения

С помощью reinterpret_cast (или приведение в стиле C) означает обход системы типов и не является переносимым и небезопасным. Правильно ли это, зависит от вашей архитектуры.
Если вы (должны) сделать это, вы намекаете, что ты знаешь что делаешь и с тех пор ты в основном сам по себе. Так много для предупреждения.

Если вы добавите номер n на указатель или тип Tпереместите этот указатель на n элементы типа T, То, что вы ищете, это тип, где 1 элемент означает 1 байт.

От sizeof раздел 5.3.3.1 .:

Оператор sizeof возвращает количество байтов в объекте
представление его операнда. […] sizeof(char), sizeof(signed
char)
а также sizeof(unsigned char) являются 1. Результат sizeof
применяется к любому другому фундаментальному типу (3.9.1)
реализации.

Обратите внимание, что нет заявления о sizeof(int), так далее.

Значение байт (раздел 1.7.1.):

Фундаментальным хранилищем в модели памяти C ++ является байт.
байт, по крайней мере, достаточно большой, чтобы содержать любой элемент основного
набор символов выполнения (2.3) и восьмибитные кодовые единицы
Unicode UTF-8 кодирует форму и состоит из непрерывной последовательности
битов, число которых определяется реализацией. […] память, доступная для программы на C ++, состоит из одной или нескольких последовательностей
смежные байты. Каждый байт имеет уникальный адрес.

Так что если sizeof возвращает количество байтов и sizeof(char) 1, чем char имеет размер одного байта для C ++. Следовательно, char является логически байт для C ++, но не обязательно де-факто стандартный 8-битный байт.
Добавление n к char* вернет указатель, который n байтов (с точки зрения модели памяти C ++). Таким образом, если вы хотите играть в опасную игру манипулирования указателем объекта побайтно, вы должны привести его к одному из char варианты.
Если ваш тип также имеет классификаторы, такие как constВы должны также перенести их в свой «тип байта».

    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);
}

пример

10

Обратите внимание, что NULL особенный. Добавление смещения на нем опасно.
reinterpret_cast не может удалить const или же volatile классификаторы. Более переносимым способом является C-style cast.
reinterpret_cast с чертами, такими как ответ @ user2218982, кажется более безопасным.

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

если у вас есть:

myType *ptr;

и вы делаете:

ptr+=3;

Компилятор наверняка увеличит вашу переменную на:

3*sizeof(myType)

И это стандартный способ сделать это, насколько я знаю.

Если вы хотите перебрать, скажем, массив элементов типа myType, это способ сделать это.

Хорошо, если вы хотите сделать это, используя

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

Или придерживайтесь простого старого C и сделайте:

myNewType *newPtr=(myNewType *) ptr;

А затем увеличить

-2
По вопросам рекламы [email protected]