Использование памяти: #define против static const для uint8_t

Я пишу библиотеку Arduino для связи с устройством I2C, и мне интересно, как лучше всего объявить все адреса регистров, чтобы сэкономить память.

Использование #defines:

#define REGISTER_MOTOR_1_MODE 0x44
#define REGISTER_MOTOR_2_MODE 0x47

Или используя static const:

static const uint8_t REGISTER_MOTOR_1_MODE = 0x44;
static const uint8_t REGISTER_MOTOR_2_MODE = 0x47;

(Очевидно, что мне нужно объявить не просто два регистра, но я подумал, что два вполне иллюстрируют эту мысль)

, 👍4

Обсуждение

define — это просто «найти и заменить», и они могут вас укусить, если вы сделаете что-нибудь кроме чисел. Например, #define LED_MASK 0x01<<2. Безопасный способ записи: #define LED_MASK (0x01<<2). См. также https://stackoverflow.com/questions/6542270/what-is-the-standard-to-declare-constant-variables-in-ansi-c., @Gerben


3 ответа


8

Вы не обнаружите заметной разницы в объеме памяти между ними.

Единственное отличие состоит в том, что метод const также назначает тип значению, что может быть полезно для перегрузки функций или математических операций.

,

Макросы также имеют типы. #define REGISTER_MOTOR_1_MODE 0x44 приведет к тому, что REGISTER_MOTOR_1_MODE будет иметь тип int. Разница больше в явности типа., @Pharap

@Pharap Почти... Макросы не имеют типа. Однако *содержимое* макроса может иметь как неявный, так и явный тип. Этот тип не имеет ничего общего с макросом., @Majenko

Тип выражения, которое заменяет макрос, по сути, является типом макроса. Если макрос не принимает аргументы, выражение всегда будет иметь один и тот же тип. Вы можете вставить макрос в decltype и получить действительный тип. Строго говоря, да, макрос исчезает к моменту начала фактической фазы компиляции, и да, не все макросы заменят допустимое выражение с типом, но дело в том, что если вы поместите макрос в код и этот макрос заменяет для допустимого выражения результирующее значение не будет «нетипизированным»., @Pharap


6

#define — это макрос препроцессора. Как говорит Гербен в своем комментарии, это просто автоматический поиск и замена.

Если вы используете его для хранения таких вещей, как строковые константы C, например, #define ERROR_STRING «Ты напортачил, приятель!», это может фактически привести к тому, что ваша программа займет больше памяти, поскольку тот же самый строковый литерал будет дублироваться каждый раз, когда вы ссылаетесь на него. В этом случае статическая константа будет одновременно безопасной для типов и сократит использование памяти.

,

Будет ли это дублироваться? Я имею в виду, разве работа компилятора не состоит в том, чтобы гарантировать: «Ты облажался, приятель!» встречается ровно один раз в таблице строковых констант?, @juhist

@juhist: C++ явно заявляет, что компилятор может дублировать идентичные строки или нет. У большинства компиляторов есть флаг, позволяющий либо делать это, либо нет. Я думаю, что по умолчанию большинство компиляторов _не_ объединяют строки, если приложение полагает, что они уникальны., @Mooing Duck

@MooingDuck Я скомпилировал программу, содержащую множество std::printf("%p\n", "foo"); линии в нем. Все они печатают один и тот же адрес., @juhist

@MooingDuck: Хотя немногие компиляторы гарантируют, что строки всегда будут объединены, я думаю, что нормальная причина заключается в том, что соблюдение такой гарантии может привести к дополнительным затратам и сложности времени сборки, а экономия памяти от слияния строк может быть, а может и не стоить того. ., @supercat

@juhist: Конечно. В вашем компиляторе и флагах компилятора. С моим компилятором и флагами я получаю разные адреса., @Mooing Duck

@supercat: Возможно, но я сомневаюсь, поскольку они в любом случае уже должны иметь аналогичную логику для всех встроенных методов (включая почти все шаблоны)., @Mooing Duck

@MooingDuck: По крайней мере, исторически хорошие компиляторы часто включали логику, чтобы в случае нехватки памяти они могли отказаться от информации, которая не требовалась для *правильной* компиляции. Компиляторы могут попытаться сохранить информацию, которая позволила бы им объединить идентичные строки или встроенные функции, но это не означает, что они обещают хранить такую информацию и использовать ее, даже если им не хватает памяти., @supercat


3

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


Однако на самом деле это означает не «выбрать любой», а то, что вам следует обратить внимание на другие факторы при принятии решения, какой из них использовать.

Как правило, вам следует избегать макросов, поскольку они не соблюдают правила области действия из-за особенностей своей работы.
В некоторых случаях это может привести к серьезным проблемам.
См. Почему макросы препроцессора являются злом и каковы альтернативы?.


Однако я хотел бы указать на третий вариант: constexpr.

constexpr uint8_t motorMode1Register = 0x44;

constexpr подразумевает const и является лучшим выражением намерения обеспечить возможность оценки значения во время компиляции.
constexpr буквально означает «это константное выражение, поэтому его можно вычислить во время компиляции».
В результате переменные constexpr можно использовать в ситуациях, когда другие переменные не могут использоваться, например, в параметрах шаблона.

См. Constexpr и макросы.

,