C Integer Promotion на 8-бітних MCU


14

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


1
Я не думаю, що компілятор зрозуміє, що змінна призначення є (непідписаним) знаком, тому вона не буде турбуватися при обчисленні перших 8 біт. Тим не менш, я виявив, що GCC іноді не настільки хороший в оптимізації коду, тому якщо ви кодуєте в ASM, результат MGIHT буде швидшим. Однак, якщо ви не робите дуже критичних за часом завдань / переривань, з дуже сильним бюджетним обмеженням, то або слід вибрати більш потужний процесор і запрограмувати його на C, або просто не турбуйтеся про меншу продуктивність (враховуйте замість часу -на ринок, краща читабельність / повторна використання коду, менша кількість помилок, екз.).
наступний хак

Прошу вибачення за те, що не встиг перевірити. Однак, я думаю, що для gcc був прапор командного рядка, який би контролював «ціле просування». Можливо, є навіть прагма, щоб контролювати її для конкретних фрагментів коду. Наскільки критична робота? У багатьох випадках використання AVR різниця швидкостей для деякої арифметики не є проблемою. Фокс про те, щоб спочатку код працював правильно. Тоді, якщо виникає проблема ефективності, з’ясуйте, що це таке. Було б легко витратити час на кодування в асемблері, тільки ви знайдете це не має значення.
gbulmer

1
просто розберіть і подивіться, що робить компілятор. З точки зору чистої мови так. реалізація тут нетипова. зазвичай int намагається вирівняти себе за розміром регістра, і якщо у вас було 16-бітових регістрів, то 8-бітова математика насправді дешевша на 16-бітну, ніж 8. Але це навпаки, і з 8-бітним mcu є сенс реалізувати int як 16 біт. тож ви, мабуть, повинні використовувати uchar там, де вам це важливо, але не робіть такої звичної звички програмування, оскільки це болить вам найбільше скрізь.
old_timer

3
Пам'ятайте: уникайте відповідей на запитання в коментарях.
труба

4
Такі питання краще задавати експертам C в 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 біт коштує дорожче. Я обдурив трохи / багато, знадобиться трохи складніші приклади, щоб побачити більше цього на справедливому шляху.

EDIT на основі обговорення коментарів

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 is fun..ny ... компілятор абсолютно неоптимізований ... Вважається, що r1 завжди є 0 в GCC, і вказує на ra, rb, {rh, rl} регістри змінних a, b та результату, компілятор міг би зробити: 1) mov rh, r1; 2) mov rl, ra; 2) додати rl, rb; 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
"компілятор зобов'язаний просувати їх до безпідписаного int". Ні, компілятор не потребує їх просування int, оскільки char, швидше за все, він не буде мати такий же рейтинг конверсій, як intна будь-якій платформі.
Лундін

3
"Наприклад, багато 8-бітних компіляторів мають прагму або комутатор командного рядка, щоб вмістити типи перерахування в 1 байт, а не int, як вимагає C." Стандарт C дає можливість розподілити змінні перерахування в 1 байті. Потрібно лише, щоб константи перерахування повинні бути int(так, це непослідовно). C11 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...
Лундін
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.