недавно, я изучаю ассемблерные коды для #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 используется. Кто-либо знает причину позади этого?
Разница в том, что при использовании #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, потому что это неоптимизированный код.
При компиляции в 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
В последнем случае компилятор рассматривает pi как переменную, а не как буквальную константу. Компилятор может оптимизировать это с помощью различных параметров компилятора.
[править] Обратите внимание, что весь записанный фрагмент можно оптимизировать, поскольку a
назначается, но не используется; объявить a
как volatile
, чтобы предотвратить это.
Семантика const
в C ++ немного отличается от семантики в C, я подозреваю, что вы получите другой результат при компиляции C ++.
Ключевое слово const означает, что конкретный файл, обращающийся к нему, не имеет права изменять его, но другие модули могут изменять или определять это значение. Поэтому сдвиги и умножения недопустимы, так как значение не известно заранее. Значения #define'ed просто заменяются на буквальное значение после препроцессирования, чтобы компилятор мог проанализировать его во время компиляции. Однако я не совсем уверен насчет перечислений.