различия в понимании справки между #define, константой и перечислением в C и C++ на уровне ассемблера

недавно, я изучаю ассемблерные коды для #define, константы и перечисления:

C коды (#define):

3   #define pi 3  
4   int main(void)
5   {
6      int a,r=1;             
7      a=2*pi*r;
8      return 0;
9   }

ассемблерные коды (для строки 6 и 7 в кодах c) сгенерированный GCC:

6   mov $0x1, -0x4(%ebp)
7   mov -0x4(%ebp), %edx
7   mov %edx, %eax
7   add %eax, %eax
7   add %edx, %eax
7   add %eax, %eax
7   mov %eax, -0x8(%ebp)

C коды (перечисление):

2   int main(void)
3   {
4      int a,r=1;
5      enum{pi=3};
6      a=2*pi*r;
7      return 0;
8   }

ассемблерные коды (для строки 4 и 6 в кодах c) сгенерированный GCC:

6   mov $0x1, -0x4(%ebp)
7   mov -0x4(%ebp), %edx
7   mov %edx, %eax
7   add %eax, %eax
7   add %edx, %eax
7   add %eax, %eax
7   mov %eax, -0x8(%ebp)

C коды (константа):

4   int main(void)
5   {
6      int a,r=1;  
7      const int pi=3;           
8      a=2*pi*r;
9      return 0;
10  }

ассемблерные коды (для строки 7 и 8 в кодах c) сгенерированный GCC:

6   movl $0x3, -0x8(%ebp)
7   movl $0x3, -0x4(%ebp)
8   mov  -0x4(%ebp), %eax
8   add  %eax, %eax
8   imul -0x8(%ebp), %eax
8   mov  %eax, 0xc(%ebp)

я нашел то использование #define и enum, ассемблерные коды являются тем же. Использование компилятора 3 добавляет инструкции выполнить умножение. Однако, когда использование const, инструкция по imul используется. Кто-либо знает причину позади этого?

5
задан martin 8 March 2010 в 10:05
поделиться

4 ответа

Разница в том, что при использовании #define или enum значение 3 не обязательно должно существовать как явное значение в коде, поэтому компилятор решил использовать две инструкции сложения, а не выделять место для константы 3. Инструкция add reg,reg занимает 2 байта на инструкцию, так что это 6 байт инструкций и 0 байт для константы умножения на 3, что меньше кода, чем imul плюс 4-байтовая константа. Кроме того, при использовании инструкции сложения получается довольно буквальный перевод *2 *3, так что это может быть не оптимизация размера, а вывод компилятора по умолчанию при умножении на 2 или на 3 (сложение обычно более быстрая инструкция, чем умножение).

#define и enum не объявляют экземпляр, они только предоставляют способ дать символическое имя значению 3, так что у компилятора есть возможность сделать код меньше.

  mov $0x1, -0x4(%ebp)    ; r=1
  mov -0x4(%ebp), %edx    ; edx = r
  mov %edx, %eax          ; eax = edx
  add %eax, %eax          ; *2
  add %edx, %eax          ; 
  add %eax, %eax          ; *3
  mov %eax, -0x8(%ebp)    ; a = eax

Но когда вы объявляете const int pi = 3, вы говорите компилятору выделить место для целочисленного значения и инициализировать его значением 3. Это занимает 4 байта, но константа теперь доступна для использования в качестве операнда для инструкции imul.

 movl $0x3, -0x8(%ebp)     ; pi = 3
 movl $0x3, -0x4(%ebp)     ; r = 3? (typo?)
 mov  -0x4(%ebp), %eax     ; eax = r
 add  %eax, %eax           ; *2
 imul -0x8(%ebp), %eax     ; *pi
 mov  %eax, 0xc(%ebp)      ; a = eax

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

xor eax, eax  ; return 0

Во всех трех случаях.

Дополнение:

Я попробовал это с MSVC и в режиме отладки получил тот же результат для всех трех случаев, MSVC всегда использует imul на литерал 6. Даже в случае 3, когда он создает const int = 3, он фактически не ссылается на него в imul.

Я не думаю, что этот тест действительно говорит вам что-то о const vs define vs enum, потому что это неоптимизированный код.

7
ответ дан 14 December 2019 в 08:48
поделиться

При компиляции в C++ генерируется идентичный код, что и при компиляции в C, по крайней мере, в GCC 4.4.1:

const int pi = 3;

...

a=2*pi*r;
-   0x40132e    <main+22>:      mov    0xc(%esp),%edx
-   0x401332    <main+26>:      mov    %edx,%eax
-   0x401334    <main+28>:      shl    %eax
-   0x401336    <main+30>:      add    %edx,%eax
-   0x401338    <main+32>:      shl    %eax
-   0x40133a    <main+34>:      mov    %eax,0x8(%esp)

Тот же код выдается, если pi определен как:

#define pi 3
0
ответ дан 14 December 2019 в 08:48
поделиться

В последнем случае компилятор рассматривает pi как переменную, а не как буквальную константу. Компилятор может оптимизировать это с помощью различных параметров компилятора.

[править] Обратите внимание, что весь записанный фрагмент можно оптимизировать, поскольку a назначается, но не используется; объявить a как volatile , чтобы предотвратить это.

Семантика const в C ++ немного отличается от семантики в C, я подозреваю, что вы получите другой результат при компиляции C ++.

0
ответ дан 14 December 2019 в 08:48
поделиться

Ключевое слово const означает, что конкретный файл, обращающийся к нему, не имеет права изменять его, но другие модули могут изменять или определять это значение. Поэтому сдвиги и умножения недопустимы, так как значение не известно заранее. Значения #define'ed просто заменяются на буквальное значение после препроцессирования, чтобы компилятор мог проанализировать его во время компиляции. Однако я не совсем уверен насчет перечислений.

0
ответ дан 14 December 2019 в 08:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: