Как компиляторы С++ фактически передают ссылочные параметры?

Этот вопрос возник в результате программирования на смешанном языке. У меня была программа 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

кроме операторов для доступа к объектам ( "." и "- > " соответственно).