Чи краще використовувати #define або const int для констант?


26

Arduino - це дивний гібрид, де деякі функції C ++ використовуються у вбудованому світі - традиційно середовищі C. Дійсно, багато коду Arduino дуже схоже на C.

C традиційно використовується #defines для констант. Для цього є ряд причин:

  1. Ви не можете встановити розміри масивів за допомогою const int.
  2. Ви не можете використовувати const intяк мітки випадок регістру (хоча це працює в деяких компіляторах)
  3. Ви не можете ініціалізувати constз іншою const.

Ви можете перевірити це питання на StackOverflow для отримання додаткових міркувань.

Отже, що нам використовувати для Arduino? Я схильний до #define, але я бачу, що якийсь код використовує, constа якийсь використовує суміш.


хороший оптимізатор зробить це суперечкою
храповиком урод

3
Дійсно? Я не бачу, як компілятор буде вирішувати такі речі, як безпека типу, не маючи можливості використовувати довжину масиву тощо.
Кібергіббони

Я згоден. Плюс до цього, якщо ви подивитесь на мою відповідь нижче, я демонструю, що існують обставини, коли ви не знаєте, який тип використовувати, тому #defineочевидний вибір. Мій приклад - називання аналогових штифтів - як A5. Для нього не існує відповідного типу, який би міг використовуватися як такий, constтому єдиний вибір - використовувати a #defineі дозволити компілятору замінити його як введення тексту перед інтерпретацією значення.
SDsolar

Відповіді:


21

Важливо зауважити, що const intвін не поводиться однаково як на C, так і на C ++, тому насправді кілька заперечень проти нього, на які було сказано в первісному запитанні та в широкій відповіді Пітера Блумфілдса, не вірні:

  • У C ++ const intконстанти - це значення часу компіляції і їх можна використовувати для встановлення меж масиву, як міток регістру тощо.
  • const intконстанти не обов'язково займають будь-яке сховище. Якщо ви не приймете їх адресу або не оголосите їх зовнішніми, вони, як правило, просто матимуть час складання.

Однак для цілих констант часто може бути кращим використовувати (з ім’ям або анонім) enum. Мені це часто подобається, тому що:

  • Він сумісний із C.
  • Це майже настільки ж безпечний тип const int(кожен біт, як безпечний тип у C ++ 11).
  • Це забезпечує природний спосіб групування споріднених констант.
  • Ви навіть можете використовувати їх для деякої кількості контролю простору імен.

Отже, в ідіоматичній програмі C ++ немає ніяких причин використовувати #defineдля визначення цілої константи. Навіть якщо ви хочете залишатися сумісними з C (через технічні вимоги, тому що ви стареньку школу, або тому, що люди, з якими ви працюєте, віддають перевагу саме цьому), ви все одно можете користуватися enumі повинні робити це, а не використовувати #define.


2
Ви піднімаєте чудові бали (особливо щодо обмежень масиву - я ще не зрозумів, що стандартний компілятор з підтримкою Arduino IDE це ще не підтримував). Не зовсім коректно сказати, що константа часу компіляції не використовує сховища, оскільки її значення все ж має відбуватися в коді (тобто пам'яті програми, а не SRAM) у будь-якому місці, де воно використовується. Це означає, що він впливає на доступний Flash для будь-якого типу, який займає більше місця, ніж покажчик.
Пітер Блумфілд

1
"так насправді кілька заперечень проти нього, на які було сказано в первісному запитанні" - чому вони не є дійсними в первісному питанні, як зазначено, це обмеження C?
Кібергіббони

@Cybergibbons Arduino заснований на C ++, тому мені незрозуміло, чому C стосуватимуться лише обмежень (якщо тільки ваш код з якихось причин не повинен бути сумісним і з C).
мікротерйон

3
@ PeterR.Bloomfield, мій погляд на константи, які не потребують додаткового зберігання, обмежився const int. Для більш складних типів ви маєте рацію, що може бути виділено сховище, але, навіть, ви навряд чи будете гірше, ніж у випадку з a #define.
мікротерйон

7

EDIT: microtherion дає чудову відповідь, яка виправляє деякі мої моменти тут, зокрема щодо використання пам'яті.


Як ви визначили, існують певні ситуації, коли ви змушені використовувати a #define, оскільки компілятор не дозволяє constзмінну. Так само в деяких ситуаціях ви змушені використовувати змінні, наприклад, коли вам потрібен масив значень (тобто ви не можете мати масив #define).

Однак є багато інших ситуацій, коли не обов’язково є однієї «правильної» відповіді. Ось декілька вказівок, яких я б дотримувався:

Безпека типу
З загальної точки зору програмування constзмінні зазвичай є кращими (де це можливо). Основна причина цього - безпека типу.

#define(Препроцесор макро) безпосередньо копіює буквене значення в кожне місце в коді, що робить кожен незалежно використання. Це може гіпотетично спричинити неоднозначності, оскільки тип може в кінцевому підсумку вирішуватися по-різному залежно від того, як / де він використовується.

constМінлива лише коли - або один тип, який визначається його декларації, і вирішені в ході ініціалізації. Він часто вимагає явного виступу, перш ніж він поводитиметься по-різному (хоча бувають різні ситуації, коли його можна сміливо рекламувати неявно). Як мінімум, компілятор може (якщо правильно налаштовано) видавати більш надійне попередження при виникненні проблеми з типом.

Можливе вирішення цього питання - включити чіткий склад або суфікс типу в #define. Наприклад:

#define THE_ANSWER (int8_t)42
#define NOT_QUITE_PI 3.14f

Такий підхід потенційно може спричинити проблеми із синтаксисом, але залежно від способу його використання.

Використання пам’яті
На відміну від обчислювальної техніки загального призначення, пам'ять, очевидно, має перевагу при роботі з чимось на зразок Arduino. Використання constзмінної та a #defineможе вплинути на збереження даних у пам'яті, що може змусити вас використовувати те чи інше.

  • const змінні (як правило) будуть зберігатися в SRAM разом з усіма іншими змінними.
  • Буквальні значення, які використовуються в #define, часто зберігатимуться в програмному просторі (флеш-пам’яті) поряд із самим ескізом.

(Зауважте, що існують різні речі, які можуть точно впливати на те, як і де зберігається щось, наприклад конфігурація компілятора та оптимізація.)

SRAM та Flash мають різні обмеження (наприклад, 2 UB та 32 KB для Uno відповідно). У деяких додатках SRAM досить легко закінчитися, тому може бути корисно перекласти деякі речі у Flash. Можливий і зворотний, хоча, мабуть, менш поширений.

ПРОГМЕМА
Можна отримати переваги безпеки типу, зберігаючи дані в програмному просторі (Flash). Це робиться за допомогою PROGMEMключового слова. Він працює не для всіх типів, але зазвичай використовується для масивів цілих чисел або рядків.

Загальна форма, наведена в документації , така:

dataType variableName[] PROGMEM = {dataInt0, dataInt1, dataInt3...}; 

Таблиці рядків трохи складніше, але в документації є повні деталі.


1

Для змінних вказаного типу, які не змінюються під час виконання, обидва зазвичай можуть бути використані.

Для цифр цифрових штифтів, що містяться у змінних, може працювати будь-яке - наприклад:

const int ledPin = 13;

Але є одна обставина, де я завжди користуюся #define

Потрібно визначити аналогові номери штифтів, оскільки вони буквено-цифрові.

Зрозуміло, ви можете жорстко кодувати номери штифтів як a2, a3і т.д. всієї програми, і компілятор буде знати, що з ними робити. Тоді якщо ви поміняєте штифти, то кожне використання потрібно змінити.

Більше того, мені завжди подобається, щоб визначення моїх контактів було вгорі все в одному місці, тому constвиникає питання про те, який тип відповідного штифта буде визначений як A5.

У тих випадках я завжди користуюся #define

Приклад дільника напруги:

//
//  read12     Reads Voltage of 12V Battery
//
//        SDsolar      8/8/18
//
#define adcInput A5    // Voltage divider output comes in on Analog A5
float R1 = 120000.0;   // R1 for voltage divider input from external 0-15V
float R2 =  20000.0;   // R2 for voltage divider output to ADC
float vRef = 4.8;      // 9V on Vcc goes through the regulator
float vTmp, vIn;
int value;
.
.
void setup() {
.
// allow ADC to stabilize
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin); delay(50);
value=analogRead(adcPin); delay(50); value=analogRead(adcPin);
.
void loop () {
.
.
  value=analogRead(adcPin);
  vTmp = value * ( vRef / 1024.0 );  
  vIn = vTmp / (R2/(R1+R2)); 
 .
 .

Усі змінні установки знаходяться вгорі, і ніколи не буде змінити значення, за adcPinвинятком часу компіляції.

Не хвилюйтеся, що це за тип adcPin. І ніяка додаткова оперативна пам’ять не використовується в двійковій системі для зберігання константи.

Перед компіляцією компілятор просто замінює кожен екземпляр adcPinрядка A5.


Є цікава тема форуму Arduino, в якій обговорюються інші способи прийняття рішення:

Змінна #define vs. const (форум Arduino)

Excertps:

Заміна коду:

#define FOREVER for( ; ; )

FOREVER
 {
 if (serial.available() > 0)
   ...
 }

Код налагодження:

#ifdef DEBUG
 #define DEBUG_PRINT(x) Serial.println(x)
#else
 #define DEBUG_PRINT(x)
#endif

Визначення trueі falseяк логічне збереження оперативної пам’яті

Instead of using `const bool true = 1;` and same for `false`

#define true (boolean)1
#define false (boolean)0

Багато чого зводиться до особистих переваг, проте зрозуміло, що #defineце більш універсально.


За тих же обставин, A constне використовуватиме більше оперативної пам'яті, ніж a #define. А для аналогових штифтів я б визначив їх як const uint8_t, хоча const intце не мало значення.
Едгар Бонет

Ви написали " a constфактично не використовує більше оперативної пам'яті [...], поки вона фактично не використовується ". Ви пропустили мою думку: більшість часу, a constне використовує оперативну пам’ять, навіть коли вона використовується . Потім " це компілятор із багатопропускною здатністю ". Найголовніше, що це оптимізуючий компілятор. По можливості константи оптимізуються в негайні операнди .
Едгар Боне
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.