简体   繁体   中英

How do C++ compilers actually pass reference parameters?

This question came about as a result of some mixed-language programming. I had a Fortran routine I wanted to call from C++ code. Fortran passes all its parameters by reference (unless you tell it otherwise).

So I thought I'd be clever (bad start right there) in my C++ code and define the Fortran routine something like this:

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

This code worked for a while but (of course right when I needed to leave) suddenly started blowing up on a return call. Clear indication of a munged call stack.

Another engineer came behind me and fixed the problem, declaring that the routine had to be defined in C++ as

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

I'd accept that except for two things. One is that it seems rather counter-intuitive for the compiler to not pass reference parameters by reference, and I can find no documentation anywhere that says that. The other is that he changed a whole raft of other code in there at the same time, so it theoretically could have been another change that fixed whatever the issue was.

So the question is, how does C++ actually pass reference parameters? Is it perhaps free to do copy-in, copy-out for small values or something? In other words, are reference parameters utterly useless in mixed-language programming? I'd like to know so I don't make this same code-killing mistake ever again.

C++ doesn't define how implementations should be, it's just a language. So there isn't "a" implementation of references.

That said, references are implemented with pointers. This leads to a lot of confusion ("references are just pointers", "references are just pointers with the mundane parts taken out") but that is not the case. References are aliases and will always be aliases.

The compiler will pass the address of a variable, and operate with that pointer. This has the same effect (but not the same semantics!). To be more concrete, a compiler might "replace" this:

void make_five(int& i)
{
    i = 5;
}

int main(void)
{
    int i = 0;
    make_five(i);
}

With this:

void make_five(int* const i)
{
    *i = 5;
}

int main(void)
{
    int i = 0;
    make_five(&i);
}

(In practice such a simple function would be inlined, but you get the point.) Hence why your colleague suggested you use a pointer.

Keep in mind references are to be preferred. This is where the distinction between references and pointers is important. Do you want to alias a variable, or do you want to point at it? Most of the times, the former. In C, you had to use a pointer to do this, and this contributes to the common C-programmer misconception that references are actually pointers.

To get similar semantics (since you are now pointing to a variable, and not aliasing it), you should ensure the value of the pointer is not 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...
}

Just to be safe.

Just to chime in, I believe you are right. I use references for passing parameters to Fortran functions all the time. In my experience, using references or pointers at the Fortran-C++ interface is equivalent. I have tried this using GCC/Gfortran, and Visual Studio/Intel Visual Fortran. It may be compiler dependent, but I think basically all compilers implement references by pointer passing.

In theory, in C++ referenced are implemneted as normal pointers. The compiler then changes the code for the function tonehave like a reference, but loading the address and then movifing indirectly the address.

Here is a small application:

void foo( int & value )
{
    value = 3;
}


void bar( int *value )
{
    *value = 3;
}

void do_test()
{
    int i;
    foo(i);
    bar(&i);
}

Lets assemble it and look at the gcc generated assemble (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

As you can see, in both functions, the compiler reads the ( stack movl 8(%ebp), %eax ), and in both calls the compiler saves the address to the stack ( leal -4(%ebp), %eax) .

The answer GMan - Save the Unico gave about C declaration might be the problem. It seems the problem is interoperability between C and fortran (at least those two compilers you are using).

No difference.

unsigned & flag

is exactly as you would write

unsigned * const flag

except for the operators to access object members ("." and "->" respectively).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM