【问题标题】:What actually happens in C++ with function parameters?函数参数在 C++ 中实际发生了什么?
【发布时间】:2026-01-01 16:20:06
【问题描述】:

过去几个月我一直在学习 C++。我知道你的第一个函数是这样声明参数的:

int myFunc(int funcVar);

然后你可以像这样将一个整数变量传递给该函数:

int x = 5;

myFunc(x);

当将参数传递给函数时,我通常认为它就像将 x 的值分配并复制到 myFunc 的参数中,在 C++ 中看起来像这样:

funcVar = x;

但是,我在声明具有引用(或指针)参数的函数时注意到:

int myFunc(int & funcVar);

我可以将变量 x 传递给 myFunc:

myFunc(x);

看起来像(在我看来):

&funcVar = x;

或者你可以传入一个实际的引用作为参数

int & rX = x;

myFunc(rX);

并且该函数也可以正常工作,在我看来就像 C++ 中的这个语句

int & funcVar = rX

将引用分配给引用是没有意义的。我的问题是编译器如何实际加载函数中的参数?我不应该认为它像将变量的值分配给函数的参数吗?

【问题讨论】:

    标签: c++ function parameter-passing


    【解决方案1】:

    调用函数时,函数的每个参数都被初始化(未赋值)。此规则与任何其他复制初始化的规则相同。所以如果你有

    int myFunc(int funcVar);
    int x = 5;
    myFunc(x);
    

    然后funcVar初始化,就好像通过这样的语句:

    int funcVar = x;
    

    如果你有

    int myFunc(int & funcVar);
    myFunc(x);
    int & rX = x;
    myFunc(rX);
    

    然后funcVar初始化(并且未分配),就像通过这样的语句:

    int & funcVar = x;
    int & funcVar = rX;
    

    引用的初始化将它绑定到由初始化器表示的对象或函数。第二个初始化确实有意义——表达式 rX 表示对象 x,因为rX 是绑定到x 的引用。因此,使用rX 初始化引用与使用x 初始化引用具有相同的效果。

    【讨论】:

    • 好的,我的问题是,分配和初始化之间有什么区别?语句 int & funcVar = x 不是将 x 的值赋给 funcVar 吗?
    • @jason 赋值将为该对象执行赋值运算符。初始化为对象分配内存并执行构造函数。
    • @Jason 对于参考,差异至关重要。初始化引用会将其绑定到对象。分配引用会将引用绑定到的对象分配给(它不会重新安装引用)。
    • 好的,我想我只是想初始化为任何语句,如 int x; vs 分配 which 将是使用 (=) 运算符的任何内容。
    【解决方案2】:

    让我们编写简单的代码和反汇编。

    int by_value(int x) { return x; }
    int by_reference(int &x) { return x; }
    int by_pointer(int *x) { return *x; }
    
    int main()
    {
        int x = 1;
    
        by_value(x);
        by_reference(x);
        by_pointer(&x);
    
        return 0;
    }
    

    $ g++ -g -O0 a.cpp ; objdump -dS a.out

    在我的环境中(x86_64, g++ (SUSE Linux) 4.8.3 20140627),结果如下。 (全文在这里http://ideone.com/Z5G8yz

    00000000004005dd <_Z8by_valuei>:
    
    int by_value(int x) { return x; }
      4005dd:       55                      push   %rbp
      4005de:       48 89 e5                mov    %rsp,%rbp
      4005e1:       89 7d fc                mov    %edi,-0x4(%rbp)
      4005e4:       8b 45 fc                mov    -0x4(%rbp),%eax
      4005e7:       5d                      pop    %rbp
      4005e8:       c3                      retq   
    
    00000000004005e9 <_Z12by_referenceRi>:
    int by_reference(int &x) { return x; }
      4005e9:       55                      push   %rbp
      4005ea:       48 89 e5                mov    %rsp,%rbp
      4005ed:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
      4005f1:       48 8b 45 f8             mov    -0x8(%rbp),%rax
      4005f5:       8b 00                   mov    (%rax),%eax
      4005f7:       5d                      pop    %rbp
      4005f8:       c3                      retq   
    
    00000000004005f9 <_Z10by_pointerPi>:
    int by_pointer(int *x) { return *x; }
      4005f9:       55                      push   %rbp
      4005fa:       48 89 e5                mov    %rsp,%rbp
      4005fd:       48 89 7d f8             mov    %rdi,-0x8(%rbp)
      400601:       48 8b 45 f8             mov    -0x8(%rbp),%rax
      400605:       8b 00                   mov    (%rax),%eax
      400607:       5d                      pop    %rbp
      400608:       c3                      retq   
    
    0000000000400609 <main>:
    
    int main()
    {
      400609:       55                      push   %rbp
      40060a:       48 89 e5                mov    %rsp,%rbp
      40060d:       48 83 ec 10             sub    $0x10,%rsp
            int x = 1;
      400611:       c7 45 fc 01 00 00 00    movl   $0x1,-0x4(%rbp)
    
            by_value(x);
      400618:       8b 45 fc                mov    -0x4(%rbp),%eax
      40061b:       89 c7                   mov    %eax,%edi
      40061d:       e8 bb ff ff ff          callq  4005dd <_Z8by_valuei>
            by_reference(x);
      400622:       48 8d 45 fc             lea    -0x4(%rbp),%rax
      400626:       48 89 c7                mov    %rax,%rdi
      400629:       e8 bb ff ff ff          callq  4005e9 <_Z12by_referenceRi>
            by_pointer(&x);
      40062e:       48 8d 45 fc             lea    -0x4(%rbp),%rax
      400632:       48 89 c7                mov    %rax,%rdi
      400635:       e8 bf ff ff ff          callq  4005f9 <_Z10by_pointerPi>
    
            return 0;
      40063a:       b8 00 00 00 00          mov    $0x0,%eax
    }
    

    by_reference(x) 和 by_pointer(&x) 一样!

    【讨论】:

    • 非常感谢您提供汇编语言表示。我还不完全了解如何阅读汇编,但一旦我这样做了,你可以确定我会参考这个答案!
    【解决方案3】:

    将一个引用分配给另一个引用是非常有意义的(当第一次定义它时,即在初始化时),这就是实际发生的情况。引用只是一个别名,因此当您将引用分配给另一个引用时,您只是说第一个引用是您分配的别名。示例

    int x = 42;
    int& rx = x;
    int& ry = rx;
    ++ry;
    std::cout << x; // displays 43
    

    Live on Coliru

    【讨论】: