Запись целого числа со знаком, как если бы оно было беззнаковым в С++

Является ли reinterpret_cast безопасным для этого, и это лучший способ сделать это?

Например, в приведенном ниже коде у меня есть класс с именем ibytestream, который позволяет читать из него uint16_ts и int16_ts. ibytestream::next это vector<unsigned char>::iterator.

inline ibytestream& operator>>(ibytestream& stream, uint16_t& data) {
    data = 0;
    data |= *stream.next++;
    data <<= 8;
    data |= *stream.next++;
    return stream;
}

inline ibytestream& operator>>(ibytestream& stream, int16_t& data) {
    return stream >> reinterpret_cast<uint16_t&>(data);
}

Я не хочу дублировать код для преобразования байтов в целое число, поэтому я использовал reinterpret_cast для версии со знаком, чтобы повторно использовать код из версии без знака. На моей машине работает нормально, но будет ли вообще работать на других современных машинах?


person Bernard    schedule 23.12.2016    source источник
comment
Не нарушит ли это строгое правило алиасинга? stackoverflow.com/q/98650/417197   -  person André    schedule 23.12.2016
comment
@Andre правило позволяет использовать псевдонимы между целочисленным типом и его подписанным/беззнаковым вариантом.   -  person M.M    schedule 23.12.2016


Ответы (2)


Да, это безопасно.

Для принятия такого решения применяются три части стандарта:

  1. Требования к выравниванию подписанных и неподписанных типов одинаковы.
  2. Разрешены приведения указателей между указателями на типы с одинаковыми требованиями к выравниванию.
  3. Когда выполняются приведения между значениями gl, приведение допустимо, если допустимо приведение между соответствующими указателями.

Для каждого стандартного целочисленного типа со знаком существует соответствующий (но другой) стандартный целочисленный тип без знака: unsigned char, unsigned short int, unsigned int, unsigned long int и unsigned long long int, каждый из которых занимает одинаковый объем памяти и имеет одинаковые требования к выравниванию.

Указатель объекта может быть явно преобразован в указатель объекта другого типа. Преобразование prvalue типа «указатель на T1» в тип «указатель на T2» (где T1 и T2 — типы объектов и требования выравнивания T2 не более строгие, чем требования T1) и обратно к его исходному типу дает исходный значение указателя.

Выражение glvalue типа T1 может быть приведено к типу «ссылка на T2», если выражение типа «указатель на T1» может быть явно преобразовано к типу «указатель на T2» с помощью reinterpret_cast. Результат ссылается на тот же объект, что и исходное glvalue, но с указанным типом.

person Sergey Kalinichenko    schedule 23.12.2016
comment
Я думаю, вы упускаете часть, на которую ссылается М. М. в своем комментарии; что вы можете получить доступ к целочисленному объекту со знаком через выражение соответствующего беззнакового типа. - person MSalters; 23.12.2016

Да, это должно быть прекрасно. (Перемещение между целыми и байтовыми массивами может иметь потенциальные проблемы с порядком байтов, но это другой вопрос, который относится как к числам со знаком, так и к числам без знака.)

Что-то совершенно другое: этот бит:

data = 0;
data |= *stream.next++;

...можно упростить:

data = *stream.next++;
person Petter Hesselberg    schedule 23.12.2016
comment
Я не думаю, что мой код будет иметь проблемы с порядком байтов. Байты всегда будут считываться с прямым порядком байтов, независимо от порядка байтов базовой машины. Кроме того, я не делал упрощения, чтобы код выглядел согласованным (то есть data |= *stream.next++; повторялся в нескольких строках), и я полагал, что мой компилятор достаточно умен, чтобы оптимизировать его. - person Bernard; 23.12.2016
comment
Это проблема только в том случае, если вам нужна переносимость между архитектурами, а, похоже, вам это не нужно. Все хорошо. - person Petter Hesselberg; 23.12.2016