Язык C: #DEFINEd значение портит 8-битное умножение. Зачем? - PullRequest
6 голосов
/ 27 апреля 2009

У меня есть следующий код 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, для небольшой встроенной ОС с именем contiki ), то результат будет равен 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 выводит правильное значение в обоих случаях.

Я не совсем уверен, почему это так. Я мог бы обойти это, предварительно назначив значение DEFINEd переменной, но я бы предпочел этого не делать.

Кто-нибудь знает, почему это так? Возможно, со ссылкой на дополнительную информацию об этом?

Ответы [ 5 ]

10 голосов
/ 27 апреля 2009

Краткий ответ: ваш компилятор глючит. (Нет проблем с переполнением, как предлагали другие.)

В обоих случаях арифметика выполняется в int, который гарантированно будет иметь длину не менее 16 бит. В первом фрагменте это потому, что 255 - это int, во втором - из-за интегрального продвижения .

Как вы заметили, gcc обрабатывает это правильно.

2 голосов
/ 29 апреля 2009

Если рассматриваемым компилятором является mspgcc, он должен выпустить список ассемблера скомпилированной программы вместе с двоичным / шестнадцатеричным файлом. Для других компиляторов могут потребоваться дополнительные флаги компилятора. Или, может быть, даже отдельный дизассемблер работает на двоичном.

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

Выполнение нескольких инструкций на ассемблере, представляющих неисправный код, должно выявить причину проблемы.

Я предполагаю, что компилятор каким-то образом оптимизирует все вычисления, так как определенная константа является известной частью во время компиляции. 255 * x можно оптимизировать до x << 8-x (что быстрее и меньше) Возможно, что-то не так с оптимизированным кодом ассемблера. </p>

Я потратил время на компиляцию обеих версий в моей системе. При активной оптимизации 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 интерпретируется как 8-разрядный со знаком -1 вместо 16-разрядного без знака 255. Или он сначала анализируется до 8 бит, а затем расширяется до 16 бит. или что угодно.

Обсуждение этой темы уже началось в списке рассылки mspgcc.

2 голосов
/ 27 апреля 2009

255 обрабатывается как целочисленный литерал и заставляет все выражение основываться на int, а не на беззнаковых символах. Второй случай заставляет тип быть правильным. Попробуйте изменить #define следующим образом:

 #define PRR_SCALE ((uint8_t) 255)
1 голос
/ 27 апреля 2009

Я не уверен, почему определение не работает, но вы можете столкнуться с ролловерами с переменными uint8_t. 255 - максимальное значение для uint8_t (2^8 - 1), поэтому, если вы умножите это значение на 3, вы неизбежно столкнетесь с некоторыми тонкими проблемами при опрокидывании.

Компилятор может оптимизировать ваш код, предварительно рассчитать результат вашего математического выражения и засунуть результат в prr (поскольку он подходит, даже если промежуточное значение не подходит).

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

prr = c * a; // rollover!
prr = prr / b;

Вам может понадобиться просто использовать больший тип данных.

0 голосов
/ 27 апреля 2009

Единственное отличие, которое я могу представить в случае 1, это

Литеральное значение PRR_SCALE может входить в ПЗУ или область кода. И может быть некоторая разница в опкоде MUL, скажем,

case-1: [register], [rom]
case -2: [register], [register]

Это может вообще не иметь смысла.

...