【问题标题】:Why does a function operates on the copy of the actual parameters?为什么函数对实际参数的副本进行操作?
【发布时间】:2019-11-09 10:30:37
【问题描述】:

我想了解将参数按值传递给函数时发生的情况。这个“函数复制值”是怎么做的?

我希望这是传递数组与传递两个变量之间的并行。

我搜索了一些线程,我认为this 最适合这些线程,但我还有其他问题。

这里有两个例子:

Ex1:

void function(int arr[])
{
    cout << arr << endl;                // The address of the first elm
    cout << sizeof(arr);                // 4 (bytes size of address on 32 bit)
}

int main()
{
    int vector[] = {1,2,3,4,5,6,7};
    function(vector);
    return 0;
}

Ex2:

void interChange(int a, int b)
{
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

int main()
{
    int a = 5, b = 3;
    interChange(a, b);
    return 0;
}

在第一个中,我想表明,即使我按值传递数组,它仍然被解释为指针(允许修改函数以改变实际的 vector 值),这就是为什么打印数组会输出一个地址,打印它的大小会输出一个指针的维度。

在第二个示例中,参数是按值传递的,但这次它们不会改变变量ab 的值。这个过程是如何发生的?为什么他们被复制而不是使用他们的地址呢?编译器是否考虑到它们的地址?如果我在main() 中打印&amp;a,然后在interChange 中打印,我会得到两个彼此非常接近的地址,例如:0x69fed80x69fe80

【问题讨论】:

  • 您可以使用godbolt.org 看看会发生什么
  • 他们是两个不同的例子。其中之一对地址进行操作。我想看看它们是否以某种方式相互关联,或者它们完全不同。
  • 在第一个示例中,您按值将指针传递给函数。在第二个示例中,您通过值将两个数字传递给函数。你的问题不清楚。转到 godbolt.org,粘贴您的代码,您可以看到 CPU 中发生了什么。地址很接近,因为变量在堆栈上,并且两个堆栈帧彼此相邻。
  • 在主函数的堆栈帧中,CPU 将值复制到寄存器中。它为函数创建一个新的堆栈帧并从寄存器中读取值。
  • 注意,如果你想让一个函数对传入的变量而不是副本进行操作,那么你可以将参数设为references

标签: c++ function parameters


【解决方案1】:

好的,这只是 C 编程语言选择的约定。 C++ 继承自 C。

您给出了两个不同但又有些相关的例子。我将分别讨论它们。

第二个例子: 例如,当您声明变量 int a 时 - 机器需要在某处存储一个值。也就是说,分配了一定数量的 RAM 来存储可以解释为 int 的值。在 x86、32 位机器上 - 这应该是 32 位 / 4 字节的内存。

当使用参数调用函数时 - 必须将值传递给函数。那是必须分配一些内存来存储这些值。 C 和 C++ 默认选择复制值。这是调用函数时发生的第一件事——分配了一些内存。因为它的参数和值被复制到新的内存中。这对整数很有效,因为它们可以存储在 CPU 寄存器中——它们的大小是有限的。如果要修改值 - 您需要获取存储值的内存地址 - 将该地址传递给函数。请注意,您已复制地址。但是拥有地址 - 指针 - 允许您更改存储在该地址的值。


// Copy two integers
void interChange(int a, int b) {
    int tmp;
    tmp = a;
    a = b;
    b = tmp;
}

void interChangePtr(int* a, int* b) {
    int tmp;
    tmp = *a;
    a* = *b;
    b* = tmp;
}

int main() {
    int a = 5, b = 3;
    interChange(a, b);
    // a=5, b=3.

    interChangePtr(&a, &b);
    // a=3, b=5

    return 0;
}

至于您的第二个示例 - 这是C 选择的另一个约定。输入时:

int main() {
 int arr[25]; // Allocates memory on the stack for 25 integers
 ...

声明一个数组(C 风格)会通知编译器您希望它为堆栈上的数组分配内存。数组只是一块连续的内存。因此,您可以获取指向它的指针并使用该指针修改值。在C 中,如果您键入arr - 这是指向堆栈上为您分配的内存的指针。因此,当您将函数 void function(int arr[]) 称为 function(arr) 时,这实际上将指针传递给您的数组,而不是实际的内存块。 这些约定的原因 - 是性能。将单个指针传递给数组然后分配新数组并复制数据会更快。

希望能给你一些进一步研究这个主题的指导。

【讨论】:

  • 如果我理解得很好,C++ 会选择复制它通过参数接收到的内容。这意味着传递function(arr),它只会传递第一个元素的地址(&amp;arr[0],相当于&arr)。但实际上里面会有一些像*pointer_inside_function = &amp;arr[0]这样的副本,所有修改都将在真实地址的副本上进行,都指向同一个内存位置?
  • 技术上arr&amp;(arr[0]) 是完全相同的地址。所以是的,在您对pointer_inside_function 操作的函数中。请注意,如果你有一个指针——你可以调用my_ptr[321] = 19,它等同于*(my_prt + 321) = 19——或者简单地说:取my_ptr的地址加上321,无论结果地址是什么——在那里存储19。 C 编译器不会检查此地址是否有效。所以在实践中,如果你 - 开发者 - 知道你有一个适当大小的数组 - 没关系。但是,如果您超出范围 - 可能会发生错误。或者破坏你的进程的内存。
【解决方案2】:

查看当你将任何参数传递给任何函数时,通常它会被复制到函数参数中。但是这里有一个例外,在数组的情况下这件事不会发生。 每当您将数组传递给任何函数时,编译器都会自动将其转换为指向数组第一个元素的指针。 现在让我们来两个案例

案例 1:当您传递一个数组编译器时,将其转换为指向该数组第一个元素的指针。现在是 int 类型的数组,因此 Ofc 指针将是 int 类型,并且 int 指针的大小为 4 字节,您可以看到。

案例 2:当您在第二个函数中传递了两个整数时。传递的参数被复制到函数参数。因此,请记住您是否在参数和参数列表中写入名称与您所做的相同。两者都是不同的变量。无论你在函数中做什么,都不会影响 main 中的变量。因此,您的交换功能没有用,因为它正在处理自己的功能的 A 和 B,而不是在 main 的末尾。

我希望现在很清楚。如果你没有得到任何部分,请评论

【讨论】:

    【解决方案3】:

    按值复制实际上意味着以下模式。

    int a = 10;
    int b = a;
    

    在这个简单的示例中,a 的值被复制到变量 b 中。

    您的第一个示例中的此函数声明

    void function(int arr[]);
    

    等价于声明

    void function(int *arr);
    

    因为编译器会隐式调整具有数组类型的参数以指向数组元素类型的指针。

    另一方面,按值传递的数组被隐式转换为指向其第一个元素的指针。

    这个函数定义

    void function(int arr[])
    {
        cout << arr << endl;                // The address of the first elm
        cout << sizeof(arr);                // 4 (bytes size of address on 32 bit)
    }
    

    及其调用

    function(vector);
    

    你可以想象如下方式

    function(vector);
    //...
    
    void function( /*int arr[] */)
    {
        int *arr = vector; 
        cout << arr << endl;                // The address of the first elm
        cout << sizeof(arr);                // 4 (bytes size of address on 32 bit)
    }
    

    也就是说,函数参数是它的局部变量,如果参数是按值传递的,那么这些局部变量会得到参数值的副本。

    但要考虑到数组的元素实际上是通过指向数组第一个元素的指针通过引用传递的。

    看起来就像你有以下函数一样

    void interChange(int *a, int *b)
    {
        int tmp;
        tmp = *a;
        *a = *b;
        *b = tmp;
    }
    

    interChange( &a, &b);
    

    要显示与将数组作为参数传递并转换为指向其第一个元素的指针的相似性,您可以通过以下方式重写函数定义

    void interChange(int *a, int *b)
    {
        int tmp;
        tmp = a[0];
        a[0] = b[0];
        b[0] = tmp;
    }
    

    这看起来像是传递给函数数组,每个数组只包含一个元素。

    【讨论】:

      猜你喜欢
      • 2016-11-19
      • 2020-06-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-16
      • 2013-08-17
      • 2012-12-06
      相关资源
      最近更新 更多