Я довольно новичок в работе с C ++ и не осознал всех тонкостей языка.
Какой самый портативный, правильный и безопасный способ добавить произвольное смещение байта для указателя любого типа в C ++ 11?
SomeType* ptr;
int offset = 12345 /* bytes */;
ptr = ptr + offset; // <--
Я нашел много ответов на Stack Overflow и Google, но все они предлагают разные вещи. Некоторые варианты, с которыми я столкнулся:
ptr = (SomeType*)(((char*)ptr) + offset);
Приведение к unsigned int
:
ptr = (SomeType*)((unsigned int)ptr) + offset);
ptr = (SomeType*)((size_t)ptr) + offset);
«Размер size_t
а также ptrdiff_t
всегда совпадают с размером указателя. Из-за этого именно эти типы должны использоваться в качестве индексов для больших массивов, для хранения указателей и арифметики указателей. «- О size_t и ptrdiff_t на CodeProject
ptr = (SomeType*)((size_t)ptr + (ptrdiff_t)offset);
Или как предыдущий, но с intptr_t
вместо size_t
, который подписан вместо неподписанного:
ptr = (SomeType*)((intptr_t)ptr + (ptrdiff_t)offset);
Только в ролях intptr_t
, поскольку offset
это уже целое число со знаком и intptr_t
не является size_t
:
ptr = (SomeType*)((intptr_t)ptr) + offset);
И во всех этих случаях безопасно ли использовать старые приведения в стиле C, или это безопаснее или более портативно для использования static_cast
или же reinterpret_cast
за это?
Должен ли я считать, что само значение указателя не подписано или подписано?
Я бы использовал что-то вроде:
unsigned char* bytePtr = reinterpret_cast<unsigned char*>(ptr);
bytePtr += offset;
С помощью 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);
}
Обратите внимание, что 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 );
}
если у вас есть:
myType *ptr;
и вы делаете:
ptr+=3;
Компилятор наверняка увеличит вашу переменную на:
3*sizeof(myType)
И это стандартный способ сделать это, насколько я знаю.
Если вы хотите перебрать, скажем, массив элементов типа myType, это способ сделать это.
Хорошо, если вы хотите сделать это, используя
myNewType *newPtr=reinterpret_cast < myNewType * > ( ptr )
Или придерживайтесь простого старого C и сделайте:
myNewType *newPtr=(myNewType *) ptr;
А затем увеличить