Пример, когда использование константы дает меньший код, чем #define - PullRequest
2 голосов
/ 31 мая 2019

В своей книге «Эффективный С ++ 3-е изд.»Скотт Мейер пишет следующие две строки кода

#define ASPECT_RATIO 1.653

и

const double AspectRatio = 1.653;

и объясняет

"... использование константы может дать меньший код, чем использованиеa #define. Это потому, что слепая подстановка препроцессором имени макроса ASPECT_RATIO с 1.653 может привести к множественным копиям 1.653 в вашем объектном коде, в то время как использование константы AspectRatio никогда не должно приводить к более чем одной копии. "

Это все еще верно для текущих компиляторов?Я немного поиграл с множественным использованием констант, но получил одинаковый размер для обоих вариантов с текущим g ++.Может быть, кто-то может показать мне рабочий пример?

Спасибо.

1 Ответ

3 голосов
/ 31 мая 2019

... в то время как использование константы AspectRatio никогда не должно приводить к более чем одной копии

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

Предварительная обработка

Содержимое макроса #define обрабатывается на этапе предварительной обработки компиляции.Содержимое макроса вставляется до начала компиляции (перевода).На простом примере:

#include <iostream>
#define THREE (3)
const int FOUR = 4;
int main()
{
    int value = THREE;
    std::cout << "Value is: " << value << "\n";
    return 0;
}

После предварительной обработки компилятор видит:

// contents of iostream header
const int FOUR = 4;
int main()
{
    int value = 3;
    std::cout << "Value is: " << value << "\n";
    return 0;
}

Макрос #define ничем не отличается от вставки числа непосредственно в код.

Поскольку число вставляется непосредственно в код, компилятор определяет тип константы (со знаком или без знака, целое число с плавающей запятой), а затем выдает код для присвоения числа переменной.

Идентификаторы / символы

Когда компилятор встречает оператор:

  const int FOUR = 4;

, компилятор создает символ "FOUR", помещает его в таблицу symbol со связанным значениемof 4. (Могут быть другие атрибуты, связанные с символом, но давайте оставим это простым для иллюстративных целей).

Когда компилятор встречает оператор, такой как:

value = FOUR;

Компилятор встречает символ "FOUR", ищет его в таблице symbol , получает значение ипродолжает обработку, аналогично обработке оператора value = 4;.

Реализация

Команды процессора, выдаваемые в любом случае, зависят от процессора и уровня оптимизации компилятора (и, возможно, сложности компилятора).

Немедленный режим
Процессоры имеют режимы доступа или выборки.Для простоты нас интересует немедленный или прямой режим доступа и косвенный режим. Немедленный режим - это место, где в инструкции есть поле для значения.Давайте назовем это MOVE (как при перемещении константы в регистр):

+--------------------------------------+    +-------+  
|         LOAD operation/instruction   |    |       |  
+--------------------+-----------------+    |       |  
+ Instruction Number | Register Number |    | Value |  
+--------------------+-----------------+    +-------+  

Инструкция MOVE состоит из двух полей: кода команды и значения для загрузки в регистр.Инструкция MOVE всегда имеет два поля. Примечание: поле значения может быть включено в единицу инструкции (слово).

В этом случае компилятор вставит число в поле значения инструкции.Никакого дополнительного пространства не требуется, никаких дополнительных инструкций.

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

+--------------------------------------+    +---------+  
|         LOAD operation/instruction   |    | Pointer |  
+--------------------+-----------------+    |    to   |  
+ Instruction Number | Register Number |    |  Value  |  
+--------------------+-----------------+    +---------+  

Непосредственный или косвенный
Некоторые процессоры могут иметь ограниченный диапазон для непосредственного значения (например, 8-бит) и чего-либо большего (например, int илиdouble), потребует косвенного доступа (дополнительное слово для указателя / адреса).Компиляторы в ленивом режиме могут упростить операции и всегда использовать косвенный режим;Непосредственный режим будет использоваться для более высоких уровней оптимизации.

При оптимизации для пространства компиляторы могут экономить место, используя косвенный режим для общих констант (например, PI).Использование постоянной переменной (вместо макроса) облегчит эту задачу.Тем не менее, компилятор может также сделать это со значением в любом случае (когда он встречает 3.14159 ... он может сохранить его в таблице для дальнейшего использования).

Сводка

Производительность и размер использования #define макроса или const переменных зависят от возможностей компилятора, уровней оптимизации и инструкций процессора.Общее утверждение о том, что макрос лучше или хуже постоянной переменной для пространства или скорости выполнения, не может быть оправдано.Слишком много зависимостей компилятора и процессора.

Общие руководящие принципы кодирования предлагают использовать постоянные переменные, поскольку они имеют тип и предотвращают дефекты, основанные на несовпадающих типах (компилятор может выдавать предупреждения или ошибки).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...