Как компиляторы C ++ фактически передают ссылочные параметры? - PullRequest
15 голосов
/ 30 мая 2010

Этот вопрос возник в результате программирования на нескольких языках. У меня была фортрановская процедура, которую я хотел вызвать из кода C ++. Fortran передает все свои параметры по ссылке (если вы не укажете иначе).

Так что я подумал, что буду умным (плохое начало) в своем коде C ++ и определю подпрограмму на Фортране примерно так:

extern "C" void FORTRAN_ROUTINE (unsigned & flag);

Этот код работал некоторое время, но (разумеется, когда мне нужно было уйти) неожиданно начал взорваться при ответном звонке. Четкое указание на стекируемый звонок.

Другой инженер пришел за мной и исправил проблему, заявив, что подпрограмма имеет , которая должна быть определена в C ++ как

extern "C" void FORTRAN_ROUTINE (unsigned * flag);

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

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

Ответы [ 4 ]

9 голосов
/ 30 мая 2010

C ++ не определяет, какими должны быть реализации, это просто язык. Таким образом, нет "a" реализации ссылок.

Тем не менее, ссылки реализованы с помощью указателей. Это приводит к большой путанице («ссылки - просто указатели», «ссылки - просто указатели с вынутыми мирскими частями»), но это , а не случай. Ссылки являются псевдонимами и всегда будут псевдонимами.

Компилятор передаст адрес переменной и будет работать с этим указателем. Это имеет тот же эффект (но не та же семантика!). Чтобы быть более конкретным, компилятор может «заменить» это:

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, что ссылки на самом деле являются указателями.

Чтобы получить аналогичную семантику (поскольку вы теперь указываете на переменную, а не на нее), вы должны убедиться, что значение указателя не равно нулю:

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...
}

Просто чтобы быть в безопасности.

4 голосов
/ 30 мая 2010

Просто вмешайтесь, я верю, что вы правы. Я использую ссылки для передачи параметров в функции Fortran все время. По моему опыту, использование ссылок или указателей в интерфейсе Fortran-C ++ эквивалентно. Я пробовал это с помощью GCC / Gfortran и Visual Studio / Intel Visual Fortran. Это может зависеть от компилятора, но я думаю, что в основном все компиляторы реализуют ссылки путем передачи указателя.

2 голосов
/ 30 мая 2010

Теоретически в C ++ упомянутые ссылки реализованы как обычные указатели. Затем компилятор изменяет код для функции тона, как ссылка, но загружает адрес и затем косвенно перемещает адрес.

Вот небольшое приложение:

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 Unico дал декларацию C. Кажется, проблема заключается в совместимости между Си и Фортраном (по крайней мере, теми двумя компиляторами, которые вы используете).

1 голос
/ 30 мая 2010

Без разницы.

без знака и флага

именно так, как вы написали бы

без знака * константный флаг

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

...