1u << 8u
равно 0x100u
, что больше, чем каждое значение uint8_t
, поэтому условие никогда не выполняется.Ваша процедура «преобразования» на самом деле просто:
return x;
, что на самом деле имеет некоторый смысл.
Вам нужно более четко определить, что вы хотите для преобразования.C99 определяет преобразование целых типов без знака в целые числа следующим образом ( §6.3.1.3 «Целые числа со знаком и без знака» )
Когда значение с целочисленным типом преобразуется в другой целочисленный типкроме _Bool
, если значение может быть представлено новым типом, оно не изменяется.
…
В противном случае новый тип подписывается и значение не может быть представлено в нем;либо результат определяется реализацией, либо генерируется определяемый реализацией сигнал.
Таким образом, значения uint8_t
между 0
и 127
сохраняются, а поведение для значений больше 127
не определено. Многие (но не все) реализации будут просто интерпретировать беззнаковые значения как представление с двойным дополнением целого числа со знаком.Возможно, вы действительно спрашиваете, как гарантировать такое поведение на разных платформах?
Если это так, вы можете использовать:
return x < 128 ? x : x - 256;
Значение x - 256
равно int
, гарантированоиметь значение x
, интерпретируемое как 8-битное целое число с дополнением до двух.Затем неявное преобразование в int8_t
сохраняет это значение.
Все это предполагает, что sint8_t
означает int8_t
, так как sint8_t
не является стандартным типом.Если это не так, то все ставки выключены, потому что правильность преобразования, которое я предложил, зависит от гарантии того, что int8_t
имеет представление, дополняющее два ( §7.18.1.1 "Целочисленные типы точной ширины" ).
Если sint8_t
вместо этого является каким-то дурацким платформо-зависимым типом, он может использовать другое представление, например ones-дополнение, которое имеет другой набор представимых значений, что делает преобразование, описанное выше, реализацией.-определено (следовательно, непереносимо) для определенных входных данных.
EDIT
Альф утверждал, что это "глупо", и что это никогда не будетнеобходимо на любой производственной системе.Я не согласен, но по общему признанию это угловой случай углового случая.Его аргумент не совсем беспочвенен.
Его утверждение, что это "неэффективно" и поэтому его следует избегать, тем не менее, является необоснованным.Разумный оптимизирующий компилятор оптимизирует это на платформах, где это не нужно.Использование GCC на x86_64, например:
#include <stdint.h>
int8_t alf(uint8_t x) {
return x;
}
int8_t steve(uint8_t x) {
return x < 128 ? x : x - 256;
}
int8_t david(uint8_t x) {
return (x ^ 0x80) - 0x80;
}
, скомпилированный с -Os -fomit-frame-pointer, дает следующее:
_alf:
0000000000000000 movsbl %dil,%eax
0000000000000004 ret
_steve:
0000000000000005 movsbl %dil,%eax
0000000000000009 ret
_david:
000000000000000a movsbl %dil,%eax
000000000000000e ret
Обратите внимание, что все три реализации идентичны после оптимизации.Clang / LLVM дает точно такой же результат.Точно так же, если мы собираем для ARM вместо x86:
_alf:
00000000 b240 sxtb r0, r0
00000002 4770 bx lr
_steve:
00000004 b240 sxtb r0, r0
00000006 4770 bx lr
_david:
00000008 b240 sxtb r0, r0
0000000a 4770 bx lr
Защита вашей реализации от угловых случаев, когда она не требует затрат для «обычного» случая, никогда не бывает «глупой».
Дляаргумент, что это добавляет ненужную сложность, я говорю: что сложнее - написать комментарий, чтобы объяснить преобразование и почему оно там, или стажер вашего преемника пытается отладить проблему через 10 лет, когда новый компилятор ломает счастливую случайностьчто ты молча зависел от всего этого времени?Неужели так трудно поддерживать следующее?
// The C99 standard does not guarantee the behavior of conversion
// from uint8_t to int8_t when the value to be converted is larger
// than 127. This function implements a conversion that is
// guaranteed to wrap as though the unsigned value were simply
// reinterpreted as a twos-complement value. With most compilers
// on most systems, it will be optimized away entirely.
int8_t safeConvert(uint8_t x) {
return x < 128 ? x : x - 256;
}
Когда все сказано и сделано, я согласен, что это неясно, но я также думаю, что мы должны попытаться ответить на вопрос за чистую монету.Конечно, лучшим решением было бы для стандарта C определить поведение преобразований из беззнакового в подписанное, когда подписанный тип представляет собой целое число из двух дополнений без дополнения (таким образом, определяя поведение для всех типов intN_t
).