Зачем использовать переменную int для вывода, когда const int, enum или #define имеют гораздо больше смысла
Почему люди используют переменную для указания номера контакта, если контакт вряд ли изменится во время выполнения кода?
Много раз я видел int
, используемый для определения вывода,
int led = 13;
при использовании const int
const int led = 13;
или enum
или #define
#define LED 13
имеет гораздо больше смысла.
Это есть даже в учебниках на сайте Arduino, например, в первом учебнике, который запускает большинство людей, Blink.
Я где-то читал что const int
предпочтительнее #define
. Почему это не поощряется с самого начала, а не позволяет людям с самого начала развивать вредные привычки? Я заметил это некоторое время назад, но в последнее время меня это начало раздражать, отсюда и вопрос.
Что касается памяти/обработки/вычислений, то это const int
, enum
или, если на то пошло, #define
, что лучше, чем простой >int
, т.е. занимает меньше памяти, хранится в другой памяти (Flash, EEPROM, SRAM), быстрее выполнение, быстрее компиляция?
Может показаться, что это дубликат Что лучше использовать #define или const int для констант?, но я задаюсь вопросом, почему люди используют переменные, и как улучшается производительность, когда они этого не делают, а не какой тип констант лучше.
@Greenonline, 👍23
Обсуждение4 ответа
Лучший ответ:
const int led = 13;
Это правильный метод. Или даже:
const byte led = 13;
Сколько у вас булавок?
Некоторые руководства не прошли такой строгий контроль качества, как могли бы.
Производительность будет выше при использовании const byte
по сравнению с int
, однако компилятор может быть достаточно умен, чтобы понять, что вы делаете.
Что вы можете сделать, так это мягко побудить людей использовать более эффективные методы, используя их в своем собственном коде.
Ответы на комментарии
Комментатор предположил, что
byte
не является стандартным C. Это верно, однако это сайт Arduino StackExchange, и я считаю, что использование стандартных типов, предоставляемых Arduino IDE, приемлемо.В Arduino.h есть такая строка:
typedef uint8_t byte;
Обратите внимание, что это не совсем то же самое, что
unsigned char
. См. uint8_t против неподписанного символа и Когда uint8_t ≠ беззнаковый символ?.Другой комментатор предположил, что использование byte не обязательно улучшит производительность, поскольку числа, меньшие, чем
int
, будут повышены доint
(см. Правила целочисленного продвижения, если вы хочу больше об этом).Однако в контексте идентификатора const компилятор в любом случае сгенерирует эффективный код. Например, дизассемблирование "blink" дает это в исходном виде:
00000086 <loop>: 86: 8d e0 ldi r24, 0x0D ; 13 88: 61 e0 ldi r22, 0x01 ; 1 8a: 1b d1 rcall .+566 ; 0x2c2 <digitalWrite>
Фактически он генерирует тот же код, что и
13
:- Это литерал.
- Является ли
#define
- Является ли
const int
- Является ли
константным байтом
Компилятор знает, когда он может поместить число в один регистр, а когда нет. Однако хорошей практикой является использование кода, указывающего ваше намерение. Создание const
дает понять, что число не изменится, а использование byte
(или uint8_t
) дает понять, что вы ожидаете небольшое количество.
Непонятные сообщения об ошибках
Еще одна важная причина избегать #define
— это сообщения об ошибках, которые вы получаете, если допустили ошибку. Рассмотрим этот скетч «мигания», в котором есть ошибка:
#define LED = 13;
void setup() {
pinMode(LED, OUTPUT); // <---- строка с ошибкой
}
void loop() {
digitalWrite(LED, HIGH); // <---- строка с ошибкой
delay(1000);
digitalWrite(LED, LOW); // <---- строка с ошибкой
delay(1000);
}
На первый взгляд все выглядит нормально, но выдает следующие сообщения об ошибках:
Blink.ino: In function ‘void setup()’:
Blink:4: error: expected primary-expression before ‘=’ token
Blink:4: error: expected primary-expression before ‘,’ token
Blink:4: error: expected `;' before ‘)’ token
Blink.ino: In function ‘void loop()’:
Blink:8: error: expected primary-expression before ‘=’ token
Blink:8: error: expected primary-expression before ‘,’ token
Blink:8: error: expected `;' before ‘)’ token
Blink:10: error: expected primary-expression before ‘=’ token
Blink:10: error: expected primary-expression before ‘,’ token
Blink:10: error: expected `;' before ‘)’ token
Вы смотрите на первую выделенную строку (строка 4) и даже не видите символа "=". Плюс линия выглядит нормально. Теперь совершенно очевидно, в чем проблема (= 13
заменяется на LED
), но когда строка находится на 400 строк ниже в коде, это не так. очевидно, проблема в том, как определяется светодиод.
Я много раз видел, как люди на это попадались (в том числе и я).
_Сколько у вас контактов?_ это очень хороший вопрос, Ник, так как на большинстве плат их количество находится только в диапазоне десятков, а не сотен (т.е. больше 255), поэтому int
является излишним... то есть до тех пор, пока Arduino наконец-то выпустила плату _Tera_... :-), @Greenonline
[C не имеет типа byte
](http://stackoverflow.com/questions/20024690/is-there-byte-data-type-in-c). Вы имеете в виду «беззнаковый символ»., @Kevin
Производительность не обязательно будет выше при использовании byte
вместо int
, поскольку в большинстве контекстов целочисленные значения типов, меньших, чем int
, повышаются до int
., @Pete Becker
C не имеет байтового типа. Вы имеете в виду беззнаковый символ.
- Мой ответ был в контексте Arduino, который имеет этот typedef uint8_t byte;
. Так что для Arduino можно использовать byte
., @Nick Gammon
Производительность не обязательно будет лучше при использовании byte вместо int
- см. исправленный пост., @Nick Gammon
Как справедливо утверждает Игнасио, это в основном потому, что они не знают лучшего. И они знают не лучше, потому что люди, которые их учили (или ресурсы, которые они использовали при обучении), не знали лучше.
Большая часть кода Arduino и учебных пособий написана людьми, которые никогда не проходили никакого обучения программированию и в значительной степени являются «самоучками» из ресурсов людьми, которые сами в значительной степени являются самоучками и не имеют должной подготовки в программировании.
р>Многие фрагменты обучающего кода, которые я вижу повсюду (и особенно те, которые доступны только в видеороликах YouTube — хм), были бы неудовлетворительными, если бы я ставил их на экзамене.
Да, const
предпочтительнее неконстантного и даже #define
, потому что:
const
(например,#define
, в отличие от неконстантного) не выделяет ОЗУconst
(аналогично неконстантному, но в отличие от#define
) придает значению явный тип
Особый интерес представляет второй пункт. Если специально не указано иное со встроенным приведением типов ((long)3
), суффиксом типа (3L
) или наличием десятичной точки (3.0),
#define
числа всегда будет целым числом, и все математические действия, выполняемые с этим значением, будут такими, как если бы оно было целым числом. В большинстве случаев это не проблема, но вы можете столкнуться с интересными сценариями при попытке #define
значения, большего, чем может хранить целое число, например, #define COUNT 70000
, а затем выполнить математическую операцию с другими значениями int
. Используя const
, вы сообщаете компилятору: «Это значение следует рассматривать как этот тип переменной», поэтому вместо этого вы должны использовать: const long count = 70000;
и все будет работать так, как ожидалось.
Это также имеет дополнительный эффект: он проверяет тип при передаче значения по месту. Попробуйте передать const long
функции, которая ожидает int
, и она будет жаловаться на сужение диапазона переменных (или даже полностью не сможет скомпилироваться в зависимости от сценария). Сделайте это с помощью #define
, и он просто молча будет выдавать вам неправильные результаты, и вы будете часами чесать голову.
Стоит отметить, что переменная const
*может* требовать оперативной памяти, в зависимости от контекста, например, если она инициализируется с использованием возвращаемого значения из функции, отличной от constexpr., @Peter Bloomfield
Аналогично, const int foo = 13; bar(&foo);
обязательно потребует от компилятора выделения фактической памяти для foo
., @Ilmari Karonen
Если вы определяете макрос, который расширяется до значения, которое не помещается в int
, компилятор рассматривает значение как имеющее наименьший тип, которому оно соответствует (правила по модулю относительно знакового и беззнакового). Если вы работаете в системе, где int
имеет длину 16 бит, #define count 70000
приведет к тому, что count
будет выглядеть как long
, так же, как если бы он был определен как const long count = 70000;
. Более того, если вы передадите любую из этих версий count функции, ожидающей int, любой нормальный компилятор будет относиться к ним одинаково., @Pete Becker
Я согласен с @PeteBecker - такая конструкция, как #define COUNT 70000
, не усекается до целого числа, но компилятор рассматривает ее как тип, достаточно большой, чтобы хранить это число. Это правда, что когда вы используете COUNT
, может быть неочевидно, что это не int, но вы все равно можете сказать то же самое о const long
., @Nick Gammon
_"#define всегда будет целым числом"_ Это неправда. Вы берете правила целочисленных литералов и применяете их к макросам препроцессора. Это всё равно, что сравнивать яблоки и поп-музыку. Выражение COUNT
в вашем примере перед компиляцией заменяется выражением 70000
, тип которого определяется правилами литералов, точно так же, как 2
, 13L
или 4.0
определяются правилами литералов. . Тот факт, что вы используете #define для псевдонимов этих выражений, не имеет значения. Если хотите, вы можете использовать #define для псевдонимов произвольных фрагментов кода C., @Lightness Races in Orbit
Как следствие, ваш последний абзац тоже неверен. Похоже, вы в корне неправильно понимаете препроцессор. Жаль, потому что остальная часть ответа была великолепной!, @Lightness Races in Orbit
Я не понимаю, о чём ты там. Я ничего не применяю к макросам. Я применяю литеральные правила к **результатам раскрытия макроса** — именно поэтому я говорю о явном приведении типов и суффиксах типов внутри макросов., @Majenko
Мой ответ: они делают это, потому что это работает. Мне трудно не задать в ответе вопрос, например: «Почему это должно быть «неправильно»?»
Отличительной чертой хорошего программиста является то, что код всегда отражает его намерения., @Ignacio Vazquez-Abrams
Мы все еще говорим об Arduino, верно? ;), @linhartr22
Arduino уже имеет плохую репутацию в широком сообществе EE из-за посредственных и ужасных аппаратных проектов, предлагаемых сообществом. Разве нам не стоит попытаться наплевать на _что-то_?, @Ignacio Vazquez-Abrams
Думаю, я начал ценить подходы «достаточно», которые распространены в сообществе разработчиков Arduino. Большинство проектов не связаны с риском для жизни или финансов, так какая разница, использует ли Blink.ino int или const int? Те, кто придерживается этого и набирается опыта, поймут, когда это важно, а когда нет., @linhartr22
«Большинство проектов не связаны с риском для жизни или финансов…» В этом нет ничего удивительного. Кто *захочет* задействовать Arduino там, где есть хоть какой-то риск, после изучения сообщества в целом., @Ignacio Vazquez-Abrams
Это «неправильно» не потому, что это не работает в какой-то конкретной ситуации, а потому, что по сравнению с тем, чтобы делать это «правильно», существует больше ситуаций, в которых это _не_ работает. Это делает код хрупким; изменения в коде могут вызвать загадочные сбои, которые съедают время отладки. Проверка типов и сообщения об ошибках компилятора помогут вам обнаружить подобные ошибки раньше, а не позже., @cjs
Как новичок в Arduino, проработавший две недели, я уловил общую идею о том, что Arduino занята непрограммистами. Большинство скетчей, которые я исследовал, в том числе на сайте Arduino, демонстрируют полное отсутствие порядка: скетчи не работают и не работают. едва ли связный комментарий в поле зрения. Блок-схемы отсутствуют, а «Библиотеки» представляют собой немодерируемую мешанину.
- Синтаксис двоичных констант
- #define выше static const int?
- Как объявить массив переменного размера (глобально)
- Как получить тип данных переменной?
- Преобразование long в массив символов и обратно
- Почему выходит ошибка: invalid conversion from 'const char*' to 'char' [-fpermissive]??
- Список препроцессоров платы Arduino #defines
- контент» не захватывается
Потому что ужасное порождает ужасное. Большинство любителей не являются опытными программистами и поэтому обучают других любителей вредным привычкам., @Ignacio Vazquez-Abrams
В частности, что касается выводов, упрощенная форма основных функций API Arduino, таких как digitalWrite, не способствует правильному встроенному дизайну, то есть использованию масок и единого адреса памяти для всего порта., @crasic