C++ 编译器实际上是如何传递引用参数的?
Posted
技术标签:
【中文标题】C++ 编译器实际上是如何传递引用参数的?【英文标题】:How do C++ compilers actually pass reference parameters? 【发布时间】:2011-02-25 13:55:13 【问题描述】:这个问题是由一些混合语言编程引起的。我有一个想从 C++ 代码调用的 Fortran 例程。 Fortran 通过引用传递其所有参数(除非您另有说明)。
所以我想我会在我的 C++ 代码中很聪明(从那里开始不好),并像这样定义 Fortran 例程:
extern "C" void FORTRAN_ROUTINE (unsigned & flag);
这段代码运行了一段时间,但是(当然是在我需要离开的时候)突然在回电时开始崩溃。清除调用堆栈的指示。
另一位工程师在我身后解决了这个问题,宣布例程必须在 C++ 中定义为
extern "C" void FORTRAN_ROUTINE (unsigned * flag);
我会接受,除了两件事。一个是编译器不通过引用传递 reference parameters 似乎相当违反直觉,而且我在任何地方都找不到说明这一点的文档。另一个是他同时更改了一大堆其他代码,所以理论上它可能是另一个解决问题的更改。
那么问题来了,C++究竟是如何传递引用参数的呢?是否可以免费为小值进行复制,复制或其他操作?换句话说,引用参数在混合语言编程中完全没用吗?我想知道,这样我就不会再犯同样的代码杀死错误了。
【问题讨论】:
通过验证阶段后,引用和指针是一回事:***.com/questions/1640355/… @CrazyJugglerDrummer:从我在那里读到的内容来看,答案实际上是“不是必需的,但对于今天的大多数编译器来说是的”。我什至会说这与我的问题相同,只是我添加了将有问题的例程定义为extern "C"
的问题。这可能会神奇地使它们相同吗?
【参考方案1】:
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...
只是为了安全。
【讨论】:
好的,但这正是我所期望的。如果它崩溃了,那么它一定是在做其他事情。那可能吗?那会是什么? 请注意,extern
声明只是一个存根。实现是在 Fortran 中。 Fortran 端不传递指针。它通过引用传递logical*4
(四字节布尔值)。它永远不会是空指针。这是有保证的。这就是为什么它似乎比指针更适合引用参数。
@TED:我还没有处理过与 Fortran 的接口,但它很可能具有 C 风格的导出,使用指针进行传递引用,并且恰好在某些情况下为您工作。跨度>
@Georg - Fortran 并没有真正的“指针”。它确实有办法实现它们的效果,但它们并不经常使用。通过引用传递参数(而不是像 C 那样的值)消除了需要指针的两个主要原因之一(另一个是动态分配)。
@T.E.D.:我的观点是,Fortran 有一个 C 而不是 C++ 接口,并且使用指针实现了传递引用。这可能导致微妙的不同行为。在 C++ 的范围内,我完全支持在指针上使用引用。【参考方案2】:
只是插话,我相信你是对的。我一直使用引用将参数传递给 Fortran 函数。根据我的经验,在 Fortran-C++ 接口上使用引用或指针是等效的。我已经使用 GCC/Gfortran 和 Visual Studio/Intel Visual Fortran 进行了尝试。它可能依赖于编译器,但我认为基本上所有编译器都通过指针传递来实现引用。
【讨论】:
我们也在使用 VS/IVF,所以这对我很有帮助。【参考方案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 之间的互操作性(至少您正在使用的这两个编译器)。
【讨论】:
【参考方案4】:没有区别。
无符号和标志
和你写的完全一样
无符号 * 常量标志
除了访问对象成员的操作符(分别为“.”和“->”)。
【讨论】:
以上是关于C++ 编译器实际上是如何传递引用参数的?的主要内容,如果未能解决你的问题,请参考以下文章