Продвижение целого числа C на 8-битных MCU

14

Используя в качестве примера avr-gcc, типы int указываются шириной 16 бит. Выполнение операций над 8-битными операндами в C приводит к тому, что эти операнды преобразуются в 16-битные типы int из-за целочисленного продвижения в C. Означает ли это, что все 8-битные арифметические операции в AVR будут намного дольше, если они записаны в C, чем если написано на ассемблере из-за целочисленного продвижения C?

pr871
источник
1
Я не думаю, что компилятор поймет, что переменная назначения является (беззнаковым) символом, поэтому он не будет беспокоиться при вычислении верхних 8 битов. Тем не менее, я обнаружил, что GCC иногда не так хорош в оптимизации кода, поэтому, если вы пишете в ASM, результат MGIHT будет быстрее. Однако, если вы не выполняете очень срочные задачи / прерывания с очень сильным ограничением бюджета, либо вы должны выбрать более мощный процессор и запрограммировать его на C, либо просто не беспокоиться о более низкой производительности (рассмотрите вместо этого время - на рынок, лучшая читаемость / возможность повторного использования кода, меньше ошибок, и т. д.).
следующий взлом
Я прошу прощения за то, что не успел проверить. Однако я думаю, что в gcc был флаг командной строки, который контролировал бы «целочисленное продвижение». Там может быть даже прагма, чтобы контролировать его для конкретных частей кода. Насколько критична производительность? Во многих случаях использования AVR разница в скорости для некоторой арифметики не является проблемой. Foxus при получении кода работает правильно в первую очередь. Тогда, если есть проблема с производительностью, выясните, что это такое. Было бы легко тратить время на кодирование на ассемблере, только если вы обнаружите, что это не имеет значения.
gbulmer
1
просто разберите и посмотрите, что делает компилятор. С чисто языковой точки зрения да. реализация здесь нетипична. обычно int пытается выровнять себя по размеру регистра, и если у вас есть 16-битные регистры, то 8-битная математика на самом деле дешевле при 16-битной, чем 8. Но это наоборот, и с 8-битной Mcu имеет смысл реализовать int как 16 бит. так что вы, вероятно, должны использовать uchar, если вам это небезразлично, но не делайте это привычкой программирования, так как это причиняет вам боль повсюду.
old_timer
3
Помните: избегайте ответов на вопросы в комментариях.
труба
4
Подобные вопросы лучше задать экспертам по Си в SO, поскольку это чисто программный вопрос. Целочисленное продвижение в C - довольно сложная тема - у среднего программиста на C будет много заблуждений по этому поводу.
Лундин

Ответы:

16

Короче:

Целочисленное повышение до 16 битов всегда имеет место - стандарт C обеспечивает это. Но компилятору разрешено оптимизировать вычисления до 8 бит (компиляторы встроенных систем обычно довольно хороши в такой оптимизации), если он может сделать вывод, что знак будет таким же, как если бы тип был повышен.

Это не всегда так! Неявные изменения подписи, вызванные целочисленным продвижением, являются распространенным источником ошибок во встроенных системах.

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

Лундин
источник
8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

как и ожидалось, fun1 - это целые числа, так же как и 16-битная математика.

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

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

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

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

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

и идеал, я знаю, что это 8-битный, хочу 8-битный результат, поэтому я просто сказал, чтобы он делал 8-битный полностью.

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

Так что в общем случае лучше стремиться к размеру регистра, который в идеале равен размеру (u) int, для 8-битного mcu, подобного этому, авторам компилятора приходилось идти на компромисс ... Дело в том, что привычка использование uchar для математики, которое, как вы знаете, не требует более 8 бит, так как когда вы перемещаете этот код или пишете новый код на процессоре с большими регистрами, теперь компилятор должен начать маскировать и подписывать расширение, что некоторые делают изначально в некоторых инструкциях, а другие нет.

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

форсирование 8 бит стоит дороже. Я немного обманул / много, мне нужно немного более сложные примеры, чтобы увидеть больше этого на справедливой основе.

РЕДАКТИРОВАТЬ на основе обсуждения комментариев

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

не удивительно. Хотя почему оптимизатор оставил эту дополнительную инструкцию, нельзя ли использовать ldi на r19? (Я знал ответ, когда спросил).

EDIT2

для авр

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

чтобы избежать вредной привычки или нет 8-битное сравнение

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

очевидно, что оптимизация была включена всего за секунду, чтобы попробовать с вашим собственным компилятором, чтобы увидеть, как он сравнивается с моим выводом, но в любом случае:

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

И да, использование байтов для переменных байтового размера, безусловно, на avr, pic и т. Д., Сэкономит вашу память, и вы действительно захотите сохранить ее ... если вы на самом деле ее используете, но, как показано здесь, как можно меньше будет в памяти, как можно больше в регистрах, поэтому экономия флэш-памяти достигается за счет отсутствия дополнительных переменных, а оперативная экономия может быть или не быть реальной ..

Старожил
источник
2
«Раньше компиляторы не делали этого, не зная, с какой версией это начало происходить, я столкнулся с этим в начале моей карьеры и, несмотря на то, что компиляторы продвигались не по порядку (как и выше), делал раскрутку, хотя я сказал, чтобы она делала учат математику, не удивлен." Это потому, что компиляторы встраиваемых систем C имели ужасное соответствие стандарту :) Компилятору обычно разрешается оптимизировать, но здесь он не может вывести, что результат будет соответствовать, unsigned charпоэтому он должен выполнить переход до 16 бит, как требуется по стандарту.
Лундин
1
@old_timer (a<<8)|bвсегда неверен для любой системы, где int16 бит. aбудет неявно повышен до intкоторого подписан. В случае, aесли в MSB хранится значение, вы в конечном итоге сдвигаете эти данные в знаковый бит 16-битного числа, что вызывает неопределенное поведение.
Лундин
1
fun3 это fun..ny ... полностью неоптимизирован компилятором ... Считается, что r1 всегда равен 0 в GCC и указывает ra, rb, {rh, rl} регистры для переменных a, b и результат, компилятор мог бы сделать: 1) mov rh, r1; 2) мов рл, ра; 2) добавить рл, рб; 3) adc rh, rh; 4) рет. 4 Инструкции, вместо 7 или 8 ... Инструкцию 1 можно изменить на ldi rh, 0.
следующий взлом
1
Это было бы лучшим ответом, если бы он указывал компилятор и соответствующие параметры в использовании.
Рассел
1
Хорошей идеей является избегать использования int / char и т. Д., А вместо этого использовать гораздо более явные и читаемые int16_t и int8_t.
пользователь
7

Не обязательно, поскольку современные компиляторы хорошо справляются с оптимизацией сгенерированного кода. Например, если вы напишите, z = x + y;где находятся все переменные unsigned char, компилятор должен их преобразовать unsigned intперед выполнением вычислений. Однако, поскольку конечный результат будет без изменений, компилятор сгенерирует код, который просто добавляет 8-битные переменные.

Конечно, это не всегда так, например, результат z = (x + y)/2;будет зависеть от старшего байта, поэтому продвижение будет происходить. Этого все еще можно избежать, не прибегая к сборке, возвращая промежуточный результат к unsigned char.

Некоторых из таких недостатков можно избежать, используя параметры компилятора. Например, многие 8-битные компиляторы имеют прагму или параметр командной строки для размещения типов перечисления в 1 байт, а не intкак того требует C.

Дмитрий Григорьев
источник
4
msgstr "компилятор требуется для их перевода в unsigned int". Нет, компилятор должен их продвигать int, поскольку charон, скорее всего, не будет иметь такой же рейтинг конверсии, как intна любой платформе.
Лундин
3
«Например, многие 8-битные компиляторы имеют прагму или параметр командной строки для размещения типов перечисления в 1 байт вместо int, как того требует C.» Стандарт C допускает размещение переменных перечисления в 1 байт. Требуется только, чтобы константы перечисления были int(да, это противоречиво). С11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Лундин