Вопрос

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

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

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

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

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

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

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

Это было полезно?

Решение

Отказ от ответственности:Я не знаю 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).

Другие советы

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

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

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

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

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

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

register int x = 10;

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

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

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

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

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

    addu    $2, $zero, $4
    jr      $ra
    nop

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

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

компилятор встраивает это с помощью

lea edx, [eax+1] 

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top