Язык C: значение #DEFINEd искажает 8-битное умножение. Почему?

У меня есть следующий код 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 переменной, но я бы не стал этого делать.

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


person Rabarberski    schedule 26.04.2009    source источник
comment
я бы, конечно, ожидал, что оба напечатают 191. во втором случае сначала c и a повышаются до int независимо, поэтому их умножение не может переполниться. То же самое происходит и в первом случае (хотя там PRR_SCALE уже является int - но это также не изменит превращения a в int). ваш gcc на вашем поле ведет себя совершенно нормально.   -  person Johannes Schaub - litb    schedule 27.04.2009
comment
убедитесь, что у вас включен заголовок stdio.h. я знаю, что один компилятор для msp430 допускает неявные объявления функций: если это так, вызов printf вызовет неопределенное поведение, и тем самым будет объяснен результат 0. просто мои два цента. не думаю, что это стоит ответа :)   -  person Johannes Schaub - litb    schedule 27.04.2009
comment
Просто из любопытства: что произойдет, если вы установите PRR_SCALE на 255U?   -  person Jochen Walter    schedule 27.04.2009
comment
@Jochen: напечатанный результат 255 !? Я понятия не имею, почему (опять же) ...   -  person Rabarberski    schedule 27.04.2009
comment
mul код для msp430 использует код эмуляции умножения (без аппаратной поддержки) iirc. так что вполне возможно, что есть какая-то ошибка, которую я подозреваю (хотя это довольно проблема C-интерфейса). В качестве исправления попробуйте (+PRR_SCALE * +a) / +b; и добавьте комментарий, что «+» будет продвигать их вручную. Может быть, это поможет?   -  person Johannes Schaub - litb    schedule 27.04.2009
comment
@litb: спасибо, но нет. Уже пробовал унарный оператор, но это не помогло. Я помещаю значение DEFINEd в переменную в качестве обходного пути на данный момент.   -  person Rabarberski    schedule 27.04.2009
comment
Компилятор, похоже, обрабатывает 255 как -1, (-1 * 3)/4 == 0, что звучит как ошибка компилятора - обработка постоянного целого числа.   -  person Skizz    schedule 27.04.2009
comment
Я согласен с другими авторами, которые считают, что ваш компилятор глючит. Согласно open-std.org/JTC1/SC22/ WG14/www/docs/n1256.pdf, раздел 6.3.1.1, параграф 2, в обоих примерах кода следует использовать int-арифметику. Вы пытались отправить отчет об ошибке команде Contiki?   -  person Jochen Walter    schedule 28.04.2009
comment
Я не знаю, возможно ли это, но если вы можете жить с ppr_scale 256, я бы сделал это со сдвигом (uint8_t)(((uint16_t)(a))‹‹8)/b) или (uint8_t)( (((uint16_t)(a))‹‹8)-a)/b) если вам действительно нужно 255.   -  person Rex Logan    schedule 05.05.2009


Ответы (5)


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

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

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

person avakar    schedule 26.04.2009
comment
арифметика выполняется в int, длина которого гарантированно не менее 16 бит. Можете ли вы предоставить источник для этого? Я сомневаюсь в его достоверности... - person strager; 27.04.2009
comment
арифметика выполняется в int: результат интегрального продвижения. [int] гарантированно имеет длину не менее 16 бит: косвенно гарантируется, что INT_MAX должно быть не менее 32767, а INT_MIN не более -32767 (см. раздел о limit.h в стандарте). - person avakar; 27.04.2009
comment
+1, это то, что я ожидал после небольшой отладки. Можете ли вы предоставить ссылку, в которой указано, как значение преобразуется обратно? Или это просто модульная арифметика? - person JaredPar; 27.04.2009
comment
+1 что еще :) кстати в случае когда бинарный оператор сталкивается с двумя операндами и парень хочет привести их к единому типу, производимые преобразования называются обычными арифметическими преобразованиями. среди конверсий, сделанных этим, есть встроенные рекламные акции, на которые вы ссылаетесь. и это то, что происходит здесь в обоих случаях, действительно. - person Johannes Schaub - litb; 27.04.2009
comment
JaredPar, если либо исходный тип, либо целевой тип преобразования подписаны, поведение определяется только в том случае, если значение может быть представлено в целевом типе. Для беззнаковых типов используется арифметика по модулю. - person avakar; 27.04.2009
comment
Конечно, на машинах с дополнением 2 преобразование в беззнаковый тип, скорее всего, будет следовать арифметике по модулю, даже если источник подписан, поэтому он будет вести себя так, как можно было бы ожидать. :) - person avakar; 27.04.2009

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

 #define PRR_SCALE ((uint8_t) 255)
person Richard    schedule 26.04.2009
comment
Не работает, я это уже пробовал (ну, я сделал приведение прямо в строке расчета. Я повторил это, как вы предложили. Без разницы). - person Rabarberski; 27.04.2009
comment
Я оба примера кода, арифметика делается в int. Это связано с продвижением целых чисел, см. open-std. .org/JTC1/SC22/WG14/www/docs/n1256.pdf, раздел 6.3.1.1, параграф 2. - person Jochen Walter; 28.04.2009

Если речь идет о компиляторе 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.

person Community    schedule 29.04.2009

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

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

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

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

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

person Andy White    schedule 26.04.2009
comment
Технически c*a не переполняется. Стандарт C указывает, что переполнение не может произойти для целочисленных типов без знака. - person JaredPar; 27.04.2009
comment
Круто, это имеет смысл. Я предполагаю, что опрокидывание было бы лучшим термином. - person Andy White; 27.04.2009

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

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

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

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

person Alphaneo    schedule 27.04.2009