Как компиляторы С++ фактически передают ссылочные параметры?
Этот вопрос возник в результате программирования на смешанном языке. У меня была программа Fortran, которую я хотел вызвать из кода на С++. Fortran передает все свои параметры по ссылке (если не указано иное).
Итак, я думал, что буду умным (плохо начинаю прямо там) в своем коде на С++ и определяю процедуру Fortran примерно так:
extern "C" void FORTRAN_ROUTINE (unsigned & flag);
Этот код работал некоторое время, но (конечно, сразу, когда мне нужно было уйти), внезапно начал взрываться при обратном вызове. Четкая индикация стека перехваченных вызовов.
Другой инженер пришел за мной и исправил проблему, объявив, что подпрограмма была определена на С++ как
extern "C" void FORTRAN_ROUTINE (unsigned * flag);
Я бы принял это, за исключением двух вещей. Один из них заключается в том, что компилятор кажется довольно противоречивым для того, чтобы не передавать ссылочные параметры по ссылке, и я не могу найти никакой документации в любом месте, где говорится об этом. Другой заключается в том, что он одновременно сменил целый плот другого кода, так что теоретически это могло бы быть еще одним изменением, которое фиксировало все, что было.
Итак, вопрос в том, как С++ действительно передает ссылочные параметры? Возможно ли бесплатное копирование, копирование для небольших ценностей или что-то еще? Другими словами, являются ли ссылочные параметры совершенно бесполезными в программировании на смешанном языке? Я хотел бы знать, поэтому я никогда больше не делаю эту ошибку с ошибкой кода.
Ответы
Ответ 1
Просто чтобы позвонить, я считаю, что ты прав. Я использую ссылки для передачи параметров в функции Fortran все время. По моему опыту, использование ссылок или указателей на интерфейсе Fortran-С++ эквивалентно. Я пробовал это с помощью GCC/Gfortran и Visual Studio/Intel Visual Fortran. Он может быть зависимым от компилятора, но я думаю, что в основном все компиляторы реализуют ссылки путем передачи указателя.
Ответ 2
С++ не определяет, как должны выполняться реализации, это просто язык. Таким образом, не существует "реализации" ссылок.
Тем не менее ссылки ссылаются на указатели. Это приводит к большой путанице ( "ссылки - это просто указатели", "ссылки - это просто указатели с выведенными обыденными частями" ), но это не так. Ссылки - это псевдонимы и всегда будут алиасами.
Компилятор передает адрес переменной и работает с этим указателем. Это имеет тот же эффект (но не ту же семантику!). Чтобы быть более конкретным, компилятор может "заменить" это:
void make_five(int& i)
{
i = 5;
}
int main(void)
{
int i = 0;
make_five(i);
}
При этом:
void make_five(int* const i)
{
*i = 5;
}
int main(void)
{
int i = 0;
make_five(&i);
}
(На практике такая простая функция была бы встроена, но вы поняли суть.) Поэтому почему ваш коллега предложил вам использовать указатель.
Имейте в виду, что ссылки предпочтительнее. Именно здесь важно различие между ссылками и указателями. Вы хотите, чтобы псевдоним переменной или вы хотите указать на нее? В большинстве случаев, первый. В C вам нужно было использовать указатель, чтобы сделать это, и это способствует общему заблуждению программиста C-программиста, что ссылки на самом деле являются указателями.
Чтобы получить аналогичную семантику (поскольку вы теперь указываете на переменную, а не накладываете ее), вы должны убедиться, что значение указателя не равно null:
extern "C" void FORTRAN_ROUTINE (unsigned * flag)
{
assert(flag); // this is normally not a problem with references,
// since the address of a variable cannot be null.
// continue...
}
Просто, чтобы быть в безопасности.
Ответ 3
В теории, в ссылках на С++ реализуются как обычные указатели. Затем компилятор изменяет код для функции tonehave как ссылку, но загружает адрес, а затем косвенно передает адрес.
Вот небольшое приложение:
void foo( int & value )
{
value = 3;
}
void bar( int *value )
{
*value = 3;
}
void do_test()
{
int i;
foo(i);
bar(&i);
}
Давайте соберем его и посмотрим на сборку gcc (gcc -s):
.file "test-params.cpp"
.text
.globl _Z3fooRi
.type _Z3fooRi, @function
_Z3fooRi:
.LFB0:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
movl $3, (%eax)
popl %ebp
ret
.cfi_endproc
.LFE0:
.size _Z3fooRi, .-_Z3fooRi
.globl _Z3barPi
.type _Z3barPi, @function
_Z3barPi:
.LFB1:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
movl 8(%ebp), %eax
movl $3, (%eax)
popl %ebp
ret
.cfi_endproc
.LFE1:
.size _Z3barPi, .-_Z3barPi
.globl _Z7do_testv
.type _Z7do_testv, @function
_Z7do_testv:
.LFB2:
.cfi_startproc
.cfi_personality 0x0,__gxx_personality_v0
pushl %ebp
.cfi_def_cfa_offset 8
movl %esp, %ebp
.cfi_offset 5, -8
.cfi_def_cfa_register 5
subl $20, %esp
leal -4(%ebp), %eax
movl %eax, (%esp)
call _Z3fooRi
leal -4(%ebp), %eax
movl %eax, (%esp)
call _Z3barPi
leave
ret
.cfi_endproc
.LFE2:
.size _Z7do_testv, .-_Z7do_testv
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
Как вы можете видеть, в обеих функциях компилятор читает (stack movl 8(%ebp), %eax
), и в обоих вызовах компилятор сохраняет адрес в стек (leal -4(%ebp), %eax)
.
Ответ GMan - Save the Unico дал о декларации C, возможно, проблема. Похоже, что проблема заключается в интероперабельности между C и fortran (по крайней мере, эти два компилятора, которые вы используете).
Ответ 4
Никакой разницы.
без знака и флаг
точно так же, как вы пишете
unsigned * const flag
кроме операторов для доступа к объектам ( "." и "- > " соответственно).