Язык C:#Определенное значение портит 8-битное умножение.Почему?
-
16-09-2019 - |
Вопрос
У меня есть следующий C-код:
#define PRR_SCALE 255
...
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
printf("prr: %u\n", prr);
Если я скомпилирую это (используя компилятор платформы msp430, для небольшой встроенной ОС, называемой контики) результат равен 0, в то время как я ожидал 191.(uint8_t задается как символ без знака)
Если я изменю его на:
uint8_t a = 3;
uint8_t b = 4;
uint8_t c = 255;
uint8_t prr;
prr = (c * a) / b;
printf("prr: %u\n", prr);
он работает правильно и выводит 191.
Компиляция простой версии этого "обычно" с использованием gcc в окне Ubuntu выводит правильное значение в обоих случаях.
Я не совсем уверен, почему это так.Я мог бы обойти это, заранее присвоив переменной определенное значение, но я бы предпочел этого не делать.
Кто-нибудь знает, почему это так?Возможно, со ссылкой на дополнительную информацию об этом?
Решение
Короткий ответ:ваш компилятор глючит.(Нет никаких проблем с переполнением, как предполагали другие.)
В обоих случаях арифметика выполняется в int
, длина которого гарантированно должна составлять не менее 16 бит.В предыдущем фрагменте это потому, что 255
является int
, в последнем случае это из - за комплексное продвижение.
Как вы отметили, gcc обрабатывает это правильно.
Другие советы
255 обрабатывается как целочисленный литерал и приводит к тому, что все выражение должно быть основано на int, а не на unsigned char.Во втором случае тип должен быть правильным.Попробуйте изменить свой #define следующим образом:
#define PRR_SCALE ((uint8_t) 255)
Если рассматриваемый компилятор является mspgcc, он должен предоставить список скомпилированной программы на ассемблере вместе с двоичным / шестнадцатеричным файлом.Другим компиляторам для этого могут потребоваться дополнительные флаги компилятора.Или, может быть, даже отдельный дизассемблер, запущенный на двоичном коде.
Это то место, где нужно искать объяснение.Из-за оптимизации компилятора фактический код, представленный процессору, может иметь не большое сходство с исходным кодом C (но обычно выполняет ту же работу).
Пошаговое выполнение нескольких инструкций ассемблера, представляющих неисправный код, должно выявить причину проблемы.
Я предполагаю, что компилятор каким-то образом оптимизирует весь процесс вычисления, поскольку определенная константа является известной частью во время компиляции.255 * x может быть оптимизирован для x<<8-x (который быстрее и меньше) Возможно, что-то идет не так с оптимизированным кодом на ассемблере.
Я потратил время на компиляцию обеих версий в своей системе.При активной оптимизации mspgcc выдает следующий код:
#define PRR_SCALE 255
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
40ce: 3c 40 fd ff mov #-3, r12 ;#0xfffd
40d2: 2a 42 mov #4, r10 ;r2 As==10
40d4: b0 12 fa 6f call __divmodhi4 ;#0x6ffa
40d8: 0f 4c mov r12, r15 ;
printf("prr: %u\n", prr);
40da: 7f f3 and.b #-1, r15 ;r3 As==11
40dc: 0f 12 push r15 ;
40de: 30 12 c0 40 push #16576 ;#0x40c0
40e2: b0 12 9c 67 call printf ;#0x679c
40e6: 21 52 add #4, r1 ;r2 As==10
Как мы можем видеть, компилятор напрямую вычисляет результат от 255*3 до -3 (0xfffd).И вот в чем проблема.Каким-то образом 255 интерпретируется как -1 8-битный знак вместо 255 16-битных без знака.Или же сначала он анализируется до 8 бит, а затем расширяется до 16 бит.или что там еще.
Обсуждение этой темы уже начато в списке рассылки mspgcc.
Я не уверен, почему определение не работает, но вы можете столкнуться с опрокидыванием с uint8_t
переменные.255 - это максимальное значение для uint8_t (2^8 - 1)
, так что если вы умножите это на 3, вы неизбежно столкнетесь с некоторыми тонкими проблемами при опрокидывании.
Компилятор может оптимизировать ваш код и предварительно вычислить результат вашего математического выражения и поместить результат в prr (поскольку он подходит, даже если промежуточное значение не подходит).
Проверьте, что произойдет, если вы нарушите свое выражение таким образом (это будет вести себя не так, как вы хотите).:
prr = c * a; // rollover!
prr = prr / b;
Возможно, вам придется просто использовать больший тип данных.
Одно отличие, о котором я могу подумать в случае-1, заключается в,
Буквальное значение PRR_SCALE может быть помещено в ПЗУ или область кода.И может быть какая-то разница в коде операции MUL, скажем,
case-1: [register], [rom]
case -2: [register], [register]
Возможно, это вообще не имеет смысла.