помочь понять различия между #define, const и enum в C и C ++ на уровне сборки - PullRequest
5 голосов
/ 08 марта 2010

В последнее время я ищу ассемблерные коды для #define, const и enum:

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)

С коды (enum):

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 инструкции add для выполнения умножения. Однако при использовании const используется инструкция imul. Кто-нибудь знает причину этого?

Ответы [ 5 ]

7 голосов
/ 08 марта 2010

Разница в том, что при #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

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

Приложение:

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

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

0 голосов
/ 13 октября 2011

Похоже, что вы на самом деле не включили оптимизатор - даже когда думали, что сделали. Я скомпилировал это:

int literal(int r)
{
  return 2*3*r;
}
int enumeral(int r)
{
  enum { pi=3 };
  return 2*pi*r;
}
int constant(int r)
{
  const int pi=3;
  return 2*pi*r;
}

... с gcc 4.2 от Apple (скорее, старше, чем используемый вами компилятор); Я могу воспроизвести сборку, которую, как вы говорите, вы получили, когда я не использую оптимизацию (по умолчанию), но на любом более высоком уровне оптимизации я получаю идентичный код для всех трех и идентичный код, независимо от того, скомпилирован ли он в C или C ++:

    movl    4(%esp), %eax
    leal    (%eax,%eax,2), %eax
    addl    %eax, %eax
    ret

Судя по комментариям к ответу Джона Кноллера, вы, похоже, не осознавали, что параметры командной строки GCC чувствительны к регистру . Капитал O варианты (-O1, -O2, -O3, -Os) включают оптимизацию; Опции o в нижнем регистре (-o whatever) определяют выходной файл. Ваша конструкция -o2 -othing молча игнорирует часть -o2 и записывает в thing.

0 голосов
/ 08 марта 2010

При компиляции в 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)

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

#define pi 3
0 голосов
/ 08 марта 2010

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

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

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

0 голосов
/ 08 марта 2010

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

...