16

这个问题是由一些混合语言编程引起的。我有一个想从 C++ 代码调用的 Fortran 例程。Fortran 通过引用传递其所有参数(除非您另有说明)。

所以我想我会在我的 C++ 代码中很聪明(从那里开始不好)并定义这样的 Fortran 例程:

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

这段代码工作了一段时间,但(当然是在我需要离开的时候)突然在回电时开始崩溃。清除调用堆栈的明确指示。

另一位工程师在我身后解决了这个问题,声明必须在 C++ 中将例程定义

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

我会接受,除了两件事。一是编译器不通过引用传递引用参数似乎相当违反直觉,而且我在任何地方都找不到任何说明这一点的文档。另一个是他同时更改了一大堆其他代码,所以理论上它可能是另一个解决问题的更改。

那么问题来了,C++究竟是如何传递引用参数的呢?是否可以免费为小值进行复制,复制或其他操作?换句话说,引用参数在混合语言编程中完全没用吗?我想知道,这样我就不会再犯同样的代码杀死错误了。

4

4 回答 4

9

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

只是为了安全。

于 2010-05-29T22:59:09.150 回答
4

只是插话,我相信你是对的。我一直使用引用将参数传递给 Fortran 函数。根据我的经验,在 Fortran-C++ 接口上使用引用或指针是等效的。我已经使用 GCC/Gfortran 和 Visual Studio/Intel Visual Fortran 进行了尝试。它可能取决于编译器,但我认为基本上所有编译器都通过指针传递来实现引用。

于 2010-05-30T00:45:29.663 回答
3

理论上,在 C++ 中,引用是作为普通指针实现的。然后编译器像引用一样更改函数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 之间的互操作性(至少您正在使用的这两个编译器)。

于 2010-05-30T06:09:44.297 回答
1

没有不同。

无符号和标志

和你写的完全一样

无符号 * 常量标志

除了访问对象成员的运算符(分别为“.”和“->”)。

于 2010-05-30T07:23:23.783 回答