2016-04-28 9 views
6

Рассмотрим функцию (одну из возможных ее реализаций), которая будет нулевать справа N бит беззнакового короткого значения (или любого другого неподписанного интегрального типа). Возможная реализация может выглядеть следующим образом:Бит-сдвиг влево и отбрасывание битов

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    using type = unsigned short; 

    constexpr type mask = ~(type(0)); 
    constexpr type right_zeros = mask << shift; // <-- error here 
    return arg & right_zeros; 
} 

int check() { 
    return zero_right<4>(16); 
} 

С помощью этого кода, все компиляторы я имею доступ жаловаться, так или иначе, о возможном переполнении. Clang является наиболее явными один, со следующими ясным сообщением:

error: implicit conversion from 'int' to 'const type' (aka 'const unsigned short') changes value from 1048560 to 65520 [-Werror,-Wconstant-conversion]

Этого код выглядит хорошо определен и ясно как день для меня, но когда 3 Составителей жалуются, я становлюсь очень нервным. Я что-то упустил? Есть ли шанс, что происходит что-то подозрительное?

P.S. В то время как альтернативные реализации zeriong из оставшихся X-бит могут быть приветствуемыми и интересными, основное внимание в этом вопросе имеет обоснованность кода, опубликованного.

+0

@TavianBarnes, они не могут быть повышен до подписанных Интсов для беззнаковых аргументов. – SergeyA

+0

Не то, о чем вы просите, но что-то, о чем вы можете знать (и не защищаете), состоит в том, что если вы оставили сдвиг беззнакового целого на «n» биты, где «n» есть> = количество бит в тип, который вы меняете, то это неопределенное поведение. –

+0

@SergeyA Но это все еще проблема: thew результат '<<' is int, а не короткий. Преобразование результата 'mask << shift' обратно в' type' перед присваиванием делает ошибку уходить. –

ответ

2

Сообщение кажется довольно простым:

error: implicit conversion from 'int' to 'const type' (aka 'const unsigned short') changes value from 1048560 to 65520 [-Werror,-Wconstant-conversion]

mask << shift имеет значение 1048560 (возникающую из 65535 << 4), и назначить его unsigned short, который определен для регулировки значения mod 65536, давая 65520.

Это последнее преобразование четко определено. Сообщение об ошибке связано с тем, что вы передали флаги компилятора -Werror,-Wconstant-conversion, чтобы в любом случае получить сообщение об ошибке. Если вы не хотите эту ошибку, не пропускайте эти флаги.


Хотя это особенно использование было хорошо определено, не может быть определено поведение для некоторых входов (а именно, shift будучи 16 или больше, если вы на ИНТ системе 32-разрядной). Поэтому вы должны исправить эту функцию.

Чтобы исправить эту функцию, вам нужно быть более осторожным в случае с корпусом unsigned short из-за крайне раздражающего правила о цельной рекламе без знака short to signed int.

Вот одно решения немного отличаются от других предложений .. избежать проблем сдвига полностью, работает для любого размера сдвига:

template<unsigned int shift, typename T> 
constexpr T zero_right(T arg) 
{ 
    T mask = -1; 
    for (int s = shift; s--;) mask *= 2u; 
    return mask & arg; 
} 

// Demo 
auto f() { return zero_right<15>((unsigned short)65535); } // mov eax, 32768 
+0

Это интересно. Я понимаю, что не могу сдвинуться на большее количество бит, чем есть в типе. В моем реальном приложении это не произойдет. Помимо этого, вы говорите, что код четко определен и всегда будет делать то, что я ожидаю от него? – SergeyA

+1

Теперь у вас есть это, полагается на дополнение 2, а неподписанный короткий случай - это реализация, предназначенная для переключения на 15, если у вас есть 32-битные ints –

+0

@SergeyA, вы можете попробовать сдвинуть больше бит, чем ширина, но Intel четко говорит они маскируют высокие биты операнда * shift *. Например, для uint32_t сдвиг равен 's% 32', поэтому' int32_t << 40' === 'int32_t << 8'. ** Но **, остерегайтесь компиляторов - если gcc видит shift> 32 в (оптимизированном) времени компиляции, он просто обнуляет результат! – BitWhistler

3

От C++ 11 стандарта на:

5.8 Shift operators [expr.shift]

1 ...

The operands shall be of integral or unscoped enumeration type and integral promotions are performed. The type of the result is that of the promoted left operand.

Выражение

mask << shift; 

оценивается после того, как интегральное продвижение применяется кmask. Следовательно, он оценивается до 1048560, если sizeof(unsigned short) равно 2, что объясняет сообщение от clang.

Один из способов избежать проблемы переполнения - сначала сдвинуть вправо, прежде чем выполнять сдвиг влево, и переместить его в свою собственную функцию.

template <typename T, unsigned int shift> 
constexpr T right_zero_bits() 
{ 
    // ~(T(0)) performs integral promotion, if needed 
    // T(~(T(0))) truncates the number to T, if needed. 
    return (T(~(T(0))) >> shift) << shift; 
} 

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    return arg & right_zero_bits<unsigned short, shift>(); 
} 
+0

Да, это объясняет сообщение, но doesn Не объясняю предупреждения. Может быть, я не совсем понимаю свой вопрос. Вопрос в том, должен ли я волноваться? :) – SergeyA

+0

@SergeyA Предупреждение - это то же самое, что вы получили бы с 'unsigned short mask = 1048560;', то есть нет, вам не следует беспокоиться, но вы должны, вероятно, подавить его явным приведением. –

+0

@TavianBarnes, я бы с удовольствием поверил вам, но для правильного ответа LanguageLawyer ему нужно что-то, чтобы обосновать заявку :) – SergeyA

2

Я не знаю, если это именно то, что вы хотите, но это не компилируется:

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    using type = unsigned short; 

    //constexpr type mask = ~(type(0)); 
    type right_zeros = ~(type(0)); 
    right_zeros <<= shift; 
    return arg & right_zeros; 
} 

int check() { 
    return zero_right<4>(16); 
} 

UPDATE:

Seems like you simply hushed the compiler by making sure it has no idea what is going on with the types.

Нет

Сначала вы получаете right_zeros со значением FFFF (от ~0). Обычно ~0 - FFFFFFFFFFFFFF..., но поскольку вы используете u16, вы получаете FFFF.

Затем сдвиг на 4 производит FFFF0 [расчет продолжается до 32 бит], но при хранении назад, остаются только крайние правые 16 бит, поэтому значение FFF0

Это совершенно законно и определить поведение и вы воспользовавшись усечением. Компилятор составляет не «обманывается». На самом деле, он отлично работает с усечением или без него.

Вы могли бы сделать right_zeros в u32 или u64, если вы хотели, но тогда вам нужно добавить right_zeros &= 0xFFFF

If there is an undefined behavior (the very essence of my question!) you simply made it undetectable.

Там нет нет UB на основе совокупности кода, независимо от того, что в компилятор говорит.

На самом деле, Тавиан получил его. Используйте явное приведение:

constexpr type right_zeros = (type) (mask << shift); // now clean 

Это говорит компилятору, среди прочего, что вы хотите усечение до 16 бит.

Если был UB, тогда компилятор все равно должен жаловаться.

+1

Похоже, вы просто замалчивали компилятор, убедившись, что он не знает, что происходит с типами , Если есть неопределенное поведение (сама суть моего вопроса!), Вы просто сделали его необнаружимым. – SergeyA

+3

«Если бы был UB, тогда компилятор все равно должен жаловаться». - не рассчитывайте на это –

3

Да, как вы подозреваете, даже после подавления диагностики компилятора ваш код, строго говоря, не полностью переносится из-за продвижения от unsigned short до подписанного int, битовая арифметика выполняется в подписанном int, а затем конвертируется int, преобразованная вернуться к unsigned short. Вам удалось избежать неопределенного поведения (я думаю, после быстрого просмотра), но результат не гарантируется тем, на что вы надеетесь. (type)~(type)0 не требуется соответствовать «всем битам один» в категории type; это уже до начала смены.

Чтобы получить что-то полностью портативное, просто убедитесь, что вы выполняете всю свою арифметику, по крайней мере, без знака int (при необходимости, более широкие типы, но не более узкие). Тогда не будет никаких рекламных акций для подписанных типов, о которых нужно беспокоиться.

template<unsigned int shift> 
unsigned short zero_right(unsigned short arg) { 
    using type = unsigned short; 

    constexpr auto mask = ~(type(0) + 0U); 
    constexpr auto right_zeros = mask << shift; 
    return arg & right_zeros; 
} 

int check() { 
    return zero_right<4>(16); 
} 
Смежные вопросы