Язык C:#Определенное значение портит 8-битное умножение.Почему?

StackOverflow https://stackoverflow.com/questions/791638

Вопрос

У меня есть следующий 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]

Возможно, это вообще не имеет смысла.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top