"函数的所有参数都按值"
传递这出现在书籍"Pointers on C" by Kenneth A Reek.
中即使形式参数是指针,我也能理解指针是如何通过值传递的。本质上,传递指针的副本,使用该指针可以访问指向的元素。我可以通过以下代码验证这一点。
//Code snippet 1
int main(void)
{
int x = 10;//&x here is d9fccc
pass_pointer(&x);
//Some other stuff
}
void pass_pointer(int *c) {
//&c here is d9fbdc [note, this is different from &x in main]
//c correctly returns d9fccc [i.e., c = &x]
//Some other stuff
}
到目前为止,非常好。
然而,当我尝试通过引用传递C ++经典时,我无法弄清楚"函数的所有参数是如何通过值"传递的。可能是真的。
请考虑以下代码。
//Code snippet 2
int main(void)
{
int x = 10;//&x here is d9fccc
pass_by_reference(x);
//Some other stuff
}
void pass_by_reference(int &c) {
//&c here returns d9fccc [note, this is same as &x in main]
//Some other stuff
}
我在缠绕头时遇到的问题如下:
答案 0 :(得分:3)
C和C ++中的逻辑不同。在C中,所有参数都按值传递。在C ++中,所有未通过引用传递的参数都按值传递。
在C和C ++中,可以通过传递指针和解除引用指针来模拟引用传递的行为。
void pass_pointer(int *p)
{
printf("%d\n", *p);
*p = 42;
}
p
本身是按值传递的,但此代码有效地使用*p
作为参考,并允许打印任何p
点的值,然后更改为{ {1}}。请注意,此ASSUMES 42
指的是有效的*p
(或等效地,int
指向有效的p
)。如果int
没有引用有效的*p
(例如int
是p
,或者指的是就程序而言并不存在的事情)然后NULL
的两个用法都给出了未定义的行为(这意味着任何事情都可能发生,但是在打印垃圾值和程序崩溃之间,常见的实际结果会有所不同)。
*c
和p
都是有效实体(指针的值,以及它指向的值)。忘记*p
是使用指针时程序员错误的常见原因,并且可能导致非常难以找到和纠正的隐秘错误。
以上示例在C和C ++中的作用相同。
在C ++中,C语言中不支持替代方法 - 通过引用传递。
*
这与void pass_reference(int &r)
{
printf("%d\n", r);
r = 42;
}
示例相同,但有一些关键变化。
pass_pointer()
通过引用传递。代码中没有指针(尽管编译器可能会发出相同的目标代码,并且像幕后指针一样处理引用,这是编译器实现的细节,而不是C ++程序员需要关心的事情。)r
- 在此函数中使用r
将无法编译。因此,可以避免忘记*r
的所有可能错误。*
(通过语言规则)保证是有效的参考。使用r
没有引入未定义的行为。因此,在r
使用之前,pass_reference()
无需检查r
是否为有效参考。答案 1 :(得分:3)
然而,当我尝试通过引用传递C ++经典时,我无法弄清楚"函数的所有参数是如何通过值"传递的。可能是真的。
这种说法有点误导。通过引用传递的语义本身就不同于传递值,您应该将它们视为单独的事物(即使编译器最终以类似方式实现它们)。 C和C ++是完全不同的语言,这是一个很好的例子。
一旦你理解了参考的目的是什么,事情就开始变得有意义了。引用某些东西是一种为它创建别名的方法,换句话说,只是用不同的名称来引用它。例如:
int a = 5;
int &b = a;
这使b
成为a
的别名。也就是说,b
是你给a
之类的一个不同的名字,但它们是同一个东西。这就是为什么您无法重新分配b
来引用其他内容的原因。就其生命周期而言,它将是变量a
的同义词。
这与指针本质上是不同的。对于前者指针中的上述代码类似于:
int a = 5;
int *b = &a;
但您可以重新指定b
指向其他内容并充当"引荐来源"相反;这是C ++引用无法做到的。
因此,当您在C ++中看到引用传递时,它意味着一些不同的东西;你只想让参数作为它所引用的对象的同义词名称。尽管可以使用指针模拟这种行为(在幕后它就是这种情况),但使用指针和使用引用将意味着你想要做的事情不同。
在C中,您不能选择通过引用传递内容,以便 使用指针模拟行为;语言不够丰富,不具备引用语义。在C ++中,引用传递是它自己的事情,你应该这样想。
示例:
这是我编写的一些代码和程序集输出,这是编译器为计算机运行所产生的代码。
#include <iostream>
using namespace std;
void print_struct(my_struct &s); // implemented elsewhere
struct my_struct {
int arr[10000];
};
int main() {
my_struct s;
for(size_t i = 0; i < 10000; ++i) { // fill array of struct
s.arr[i] = i;
}
my_struct &s_ref = s; // print reference to s
print_struct(s_ref);
return 0;
}
下面是装配输出没有任何优化(O0)。注意s的地址如何存储在堆栈中。这是作为print_struct的参数传递的,只是s
的地址。
000000000040085a <main>:
40085a: 55 push %rbp
40085b: 48 89 e5 mov %rsp,%rbp
40085e: 48 81 ec 50 9c 00 00 sub $0x9c50,%rsp
400865: 48 c7 85 b0 63 ff ff movq $0x0,-0x9c50(%rbp) # the counter i is stored at -0x9c50
40086c: 00 00 00 00
400870: eb 1f jmp 400891 <main+0x37>
400872: 48 8b 85 b0 63 ff ff mov -0x9c50(%rbp),%rax
400879: 89 c2 mov %eax,%edx
40087b: 48 8b 85 b0 63 ff ff mov -0x9c50(%rbp),%rax
400882: 89 94 85 c0 63 ff ff mov %edx,-0x9c40(%rbp,%rax,4) # struct s starts at -0x9c40
400889: 48 83 85 b0 63 ff ff addq $0x1,-0x9c50(%rbp)
400890: 01
400891: 48 81 bd b0 63 ff ff cmpq $0x270f,-0x9c50(%rbp)
400898: 0f 27 00 00
40089c: 76 d4 jbe 400872 <main+0x18>
40089e: 48 8d 85 c0 63 ff ff lea -0x9c40(%rbp),%rax
4008a5: 48 89 85 b8 63 ff ff mov %rax,-0x9c48(%rbp) # stores address of s at -0x9c48 which is what s_ref is
4008ac: 48 8b 85 b8 63 ff ff mov -0x9c48(%rbp),%rax
4008b3: 48 89 c7 mov %rax,%rdi # the address stored at -0x9c48 is passed as parameter to print_struct method
4008b6: e8 72 ff ff ff callq 40082d <_Z12print_structR9my_struct>
4008bb: b8 00 00 00 00 mov $0x0,%eax
4008c0: c9 leaveq
4008c1: c3 req
通过一些基本的典型优化,组件如下所示。请注意,在这种情况下,引用永远不会存储在堆栈中。
000000000040099a <main>:
40099a: 48 81 ec 48 9c 00 00 sub $0x9c48,%rsp # s is stored at 0x9c48
4009a1: b8 00 00 00 00 mov $0x0,%eax # counter of for loop stored in register
4009a6: eb 07 jmp 4009af <main+0x15>
4009a8: 89 04 84 mov %eax,(%rsp,%rax,4)
4009ab: 48 83 c0 01 add $0x1,%rax
4009af: 48 3d 0f 27 00 00 cmp $0x270f,%rax
4009b5: 76 f1 jbe 4009a8 <main+0xe>
4009b7: 48 89 e7 mov %rsp,%rdi # doesn't put address of s onto stack. Just passes the address straight as a parameter to print_struct
4009ba: e8 42 ff ff ff callq 400901 <_Z12print_structR9my_struct>
4009bf: b8 00 00 00 00 mov $0x0,%eax
4009c4: 48 81 c4 48 9c 00 00 add $0x9c48,%rsp
4009cb: c3 retq
答案 2 :(得分:2)
您正在考虑2种不同语言的2种不同情况。
在C 函数的所有参数都按值传递
通过引用传递只是对象的相同引用。任何变化都反映在被叫方。
在通过引用传递(也称为传递地址)时,副本 存储实际参数的地址。使用时通过引用传递 您正在更改客户端程序传入的参数。
我们也可以在传值环境中以某种形式模仿传递(不完全)。
在指针的情况下你需要有2个内存访问。但是如果引用它会被自动解除引用。
如果您看一下gcc生成的汇编,那么您将看到在引用的情况下也传递指针。但唯一的问题就是我们直接访问它而不是通过其他变量(如C指向函数的指针传递)
编写一个c ++代码,只需使用指针,也可以通过引用传递。
然后使用gcc -S progname.c
并检查progname.s
文件......您将看到存储的地址。
答案 3 :(得分:2)
理解参考文献的最佳方法是理解两个规则:
引用与指针相同,除了:
每次使用引用时,它都会被*
运算符自动解除引用,作为使用它之前的第一个业务顺序。
嗯,还有第三条规则:
&
,运算符应用于您创建引用的任何内容,那就是。......但这对于本次讨论而言并不重要。无论如何,前两条规则是最重要的规则。如果你记住这些规则,那么你可以考虑:
int &c
与以下内容相同:
int *c
除非每次在任何上下文中使用此c
,否则它会自动解除引用,就像您编写(*c)
一样使用,所以在以下表达式中使用:
int a = b + c;
如果c
是引用,则相当于int a = b + (*c);
。
如果你做了这个心理调整 - 用指针替换一个引用变量,并在实际使用的任何地方打一个dereference操作符 - 你应该能够自己找出你自己的问题的答案,并理解当引用传递给函数时,它们通过值传递,就像其他所有传递一样。在这里,他们没有什么不同。