views:

364

answers:

5

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.

+4  A: 

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.

GMan
OK, but that's what I was expecting it to do. If that is crashing, then it must be doing something else. Is that possible? What would that something else be?
T.E.D.
Note that the `extern` declaration is just a stub. The implementation is in Fortran. The Fortran side is *not* passing a pointer. It is passing a `logical*4` (four-byte boolean) *by reference*. It will never be a null pointer. That is guaranteed. That's why it seemed like a reference parameter would be a better fit than a pointer.
T.E.D.
@T.E.D.: I haven't dealt with interfacing Fortran, but it most likely has C-style exports using pointers for pass-by-reference and just happened to work for you under certain circumstances.
Georg Fritzsche
@Georg - Fortran doesn't really have "pointers". It does have ways to achieve the effect of them, but they aren't often used. Passing arguments by reference (instead of value like C) gets rid of one of the two main reasons for needing pointers (the other being dynamic allocation).
T.E.D.
@T.E.D.: My point is that Fortran has a C, not C++, interface, and implements pass-by-reference with pointers. That *may* result in subtly different behaviour. Inside the bounds of C++ i'm all for using references over pointers.
Georg Fritzsche
+3  A: 

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.

Victor Liu
We are using VS/IVF as well, so this is a very helpful comment to me.
T.E.D.
+2  A: 

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

elcuco
A: 

No difference.

unsigned & flag

is exactly as you would write

unsigned * const flag

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

Poni
A: 

Is it legal to declare a function with C linkage when it would not be valid C? The C++ reference does not exist in C. While it may end up being implemented the same as a pointer, is it not possible that using it this way might confuse the compiler or linker, making the result unpredictable?

oht
It might be. Afaik, C linkage just means no name mangling usually. The assembly has no concept of function arguments.
Maister