Как передача аргументов c ++ by-ref компилируется в сборке? - PullRequest
4 голосов
/ 08 мая 2009

В последние годы колледжа у меня был курс по компиляторам. Мы создали компилятор для подмножества C. Мне всегда было интересно, как вызов функции pass-by-ref компилируется в сборку в C ++.

Насколько я помню, вызов функции pass-by-val следует следующей процедуре:

  • Хранить адрес ПП
  • Поместить аргументы в стек
  • Выполнить вызов функции
  • В функции извлекаются из стека параметры

Что отличается для передачи по ссылке? (int void (int &);)

EDIT:

Возможно, я звучу совершенно растерянно, но, если бы вы могли мне помочь, я был бы очень благодарен.

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

struct A {
    int x;
    A(int v){
        x = v;
    }
};

int byRef(A& v){
    v = A(3);
    return 0;
}

int byP   (A* v){
    v = &A(4); //OR new A(4)
    return 0;
}

int _tmain(int argc, _TCHAR* argv[])
{
    A a (1); A b (2);
    byRef(a); byP  (&b);
    cout << a.x << " " << b.x;

    system("pause");

    return 0;
}

Я знаю, что в byP (A *) v передается по значению, поэтому это не повлияет на аргумент вызывающей стороны. Тогда как бы вы реализовали byRef (A &) с точки зрения A *?

Ответы [ 8 ]

6 голосов
/ 08 мая 2009

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

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

4 голосов
/ 08 мая 2009
int byRef(A& v){
  v = A(3);
  return 0;
}

Это вызывает назначение временного объекта объекту, переданному по ссылке, объект, используемый в вызове функции, изменяется. Полное копирование будет выполнено, если не указан оператор присваивания.

int byP   (A* v){
  v = &A(4); //OR new A(4)
  return 0;
}

Копирует указатель на временный объект в переданное значение указателя. Функция присваивания не вызывается. Значение 'v' изменяется, но объект v, на который указывает объект, адрес объекта, переданный в качестве аргумента, остается неизменным.

Если вы сделали это:

struct A {
  int x;
  A(int v){
    x = v;
  }
  A &operator = (A &rhs){
    cout << "assignment!";
  }
};

, тогда «назначение» будет выводиться в функции byRef, но не в функции byP.

Хотя & реализован с использованием указателей «под капотом», как уже говорили другие, они рассматриваются как объект, переданный функции в язык.

Итак, чтобы реализовать byRef с помощью указателей:

int byRefUsingP (A *v)
{
  *v = A(3);
  // or you could do:
  // v->operator = (A(3));
  // if an operator = is defined (don't know if it will work without one defined)
  return 0;
}
4 голосов
/ 08 мая 2009

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

Редактировать: Ваш пример указателя должен быть:

int byP (A* v) {
    * v = A(4);    // modify thing referenced by the pointer
    return 0;
}
0 голосов
/ 30 сентября 2009

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

При передаче по ссылке адрес структуры помещается в стек, который является просто int. Это самый эффективный способ пропускать большие объекты.

При передаче по указателю адрес указателя помещается в стек. Указатель должен быть разыменован, чтобы получить адрес структуры. Это дополнительная операция.

Кстати, в функции byP есть серьезная ошибка: она присваивает указателю временную локальную переменную. Структура размещается в стеке и будет (очень вероятно) перезаписана после выхода из области видимости. Вы должны использовать «new», чтобы выделить его в куче, или присвоить struct значению, на которое ссылается указатель, как в примере Neil Butterworth.

0 голосов
/ 08 мая 2009

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

[ПРЕДУПРЕЖДЕНИЕ О СПОЙЛЕРЕ]

.

Это указатель.

0 голосов
/ 08 мая 2009

Я думаю, что в проходном режиме процедура будет такой: * Храните стоимость ПП * Вставьте аргументы в стек * Выполнить вызов функции * В функции извлекаются из стека параметры

и в pass-by-ref процедура будет той, которую вы написали

ура

0 голосов
/ 08 мая 2009

Я не смотрел на что-то вроде реализации GCC, но общая идея заключается в том, что вместо передачи содержимого переменной вы передаете ее адрес (так же, как если бы код C ++ использовал «*» вместо «&» для параметра).

Доступны другие схемы, но обычно это делается путем помещения значений (или адресов) в стек.

0 голосов
/ 08 мая 2009

Разница в том, что он передает адрес параметра, а не значение параметра.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...