Использование регистра ЦП C ++ - PullRequest
10 голосов
/ 02 декабря 2009

В C ++ локальные переменные всегда размещаются в стеке. Стек является частью разрешенной памяти, которую может занимать ваше приложение. Эта память хранится в вашей оперативной памяти (если не выгружается на диск). Теперь компилятор C ++ всегда создает код ассемблера, который хранит локальные переменные в стеке?

Взять, к примеру, следующий простой код:

int foo( int n ) {
   return ++n;
}

В коде ассемблера MIPS это может выглядеть так:

foo:
addi $v0, $a0, 1
jr $ra

Как видите, мне вообще не нужно было использовать стек для n. Будет ли компилятор C ++ распознавать это и напрямую использовать регистры процессора?

Редактировать: Ого, большое спасибо за ваши почти немедленные и обширные ответы! Тело функции foo, конечно, должно быть return ++n;, а не return n++;. :)

Ответы [ 6 ]

12 голосов
/ 02 декабря 2009

Да. Нет правила, что «переменные всегда размещаются в стеке». Стандарт C ++ ничего не говорит о стеке. Он не предполагает, что стек существует или существуют регистры. Это просто говорит о том, как должен вести себя код, а не как он должен быть реализован.

Компилятор хранит переменные в стеке только тогда, когда он должен - когда они должны жить, например, после вызова функции или если вы пытаетесь взять их адрес.

Компилятор не глупый. ;)

9 голосов
/ 02 декабря 2009

Отказ от ответственности: я не знаю MIPS, но я знаю немного x86, и я думаю, что принцип должен быть таким же ..

В обычном соглашении о вызове функции компилятор помещает значение n в стек, чтобы передать его функции foo. Однако существует соглашение fastcall, которое вы можете использовать, чтобы указать gcc вместо этого передавать значение через регистры. (MSVC также имеет эту опцию, но я не уверен, каков ее синтаксис.)

test.cpp:

int foo1 (int n) { return ++n; }
int foo2 (int n) __attribute__((fastcall));
int foo2 (int n) {
    return ++n;
}

Компиляция выше с g++ -O3 -fomit-frame-pointer -c test.cpp, я получаю для foo1:

mov eax,DWORD PTR [esp+0x4]
add eax,0x1
ret

Как видите, он считывает значение из стека.

А вот и foo2:

lea eax,[ecx+0x1]
ret

Теперь оно берет значение непосредственно из регистра.

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

Отказ от ответственности 2: Я не говорю, что вы должны постоянно угадывать компилятор. Это, вероятно, не практично и необходимо в большинстве случаев. Но не думайте, что он создает идеальный код.

Редактировать 1: Если вы говорите о простых локальных переменных (а не аргументах функций), то да, компилятор будет размещать их в регистрах или в стеке так, как считает нужным.

Редактировать 2: Похоже, что соглашение о вызовах зависит от архитектуры, и MIPS передаст первые четыре аргумента в стеке, как заявил Ричард Пеннингтон в своем ответе. Так что в вашем случае вам не нужно указывать дополнительный атрибут (который на самом деле является специфичным для x86 атрибутом).

8 голосов
/ 02 декабря 2009

Да, хороший оптимизирующий C / C ++ это оптимизирует. И даже МНОГО больше: См. Здесь: Опрос Феликса фон Лейтнерса .

Обычный компилятор C / C ++ не помещает все переменные в стек в любом случае. Проблема с вашей foo() функцией может заключаться в том, что переменная может передаваться через стек в функцию (это определяет ABI вашей системы (аппаратное обеспечение / ОС)).

С ключевым словом C register вы можете дать компилятору подсказку , что, вероятно, было бы хорошо сохранить переменную в регистре. Пример:

register int x = 10;

Но помните: компилятор может не хранить x в регистре, если захочет!

6 голосов
/ 02 декабря 2009

Ответ - да, может быть. Это зависит от компилятора, уровня оптимизации и целевого процессора.

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

На самом деле, правда страннее, чем вымысел. В вашем случае параметр возвращается без изменений: возвращается значение n перед оператором ++:

foo:
    .frame  $sp,0,$ra
    .mask   0x00000000,0
    .fmask  0x00000000,0

    addu    $2, $zero, $4
    jr      $ra
    nop
2 голосов
/ 02 декабря 2009

Так как ваша примерная функция foo является функцией тождества (она просто возвращает свой аргумент), мой компилятор C ++ (VS 2008) полностью удаляет этот вызов функции. Если я изменю это на:

int foo( int n ) {
   return ++n;
}

компилятор указывает это на

lea edx, [eax+1] 
0 голосов
/ 24 июля 2017

Да, регистры используются в C ++. MDR (регистры данных памяти) содержат данные, извлекаемые и сохраняемые. Например, чтобы извлечь содержимое ячейки 123, мы должны загрузить значение 123 (в двоичном виде) в MAR и выполнить операцию выборки. Когда операция завершена, копия содержимого ячейки 123 будет в MDR. Чтобы сохранить значение 98 в ячейке 4, мы загружаем 4 в MAR и 98 в MDR и выполняем сохранение. Когда операция будет завершена, содержимое ячейки 4 будет установлено равным 98, отбрасывая все, что было ранее. Регистры данных и адресов работают с ними для достижения этой цели. В C ++ также, когда мы инициализируем var значением или запрашиваем его значение, происходит то же самое.

И еще одна вещь: современные компиляторы также выполняют распределение регистров, что несколько быстрее, чем выделение памяти.

...