Подтвердить что ты не робот

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

Я совсем недавно работаю с С++ и не понял всех тонкостей и тонкостей языка.

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

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

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

  • Вставить в char *:

    ptr = (SomeType*)(((char*)ptr) + offset);
    
  • Вставить в unsigned int:

    ptr = (SomeType*)((unsigned int)ptr) + offset);
    
  • Вставить в size_t:

    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, который подписан вместо unsigned:

    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 для этого?

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

Если вы добавите число 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.):

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

Итак, если sizeof возвращает количество байтов, а sizeof(char) равно 1, чем char имеет размер одного байта до С++. Следовательно, char логически является байтом для С++, но необязательно стандартным 8-разрядным байтом де-факто. Добавление n в char* приведет к возврату указателя, который является n байтами (в терминах модели памяти С++). Таким образом, если вы хотите поиграть в опасную игру манипуляции указателем на объект, вы должны отдать его одному из вариантов 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);
    }

Пример

Ответ 3

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

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

Ответ 4

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

myType *ptr;

и вы выполните:

ptr+=3;

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

3*sizeof(myType)

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

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

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

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

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

myNewType *newPtr=(myNewType *) ptr;

И затем increment