【问题标题】:What happens to a parameter passed by value that is modified locally?本地修改的按值传递的参数会发生什么情况?
【发布时间】:2017-07-18 11:43:43
【问题描述】:

我很清楚,在 C/C++ 函数之外修改按值传递的函数参数是无效的,但编译器允许这样做 - 但会发生什么?是否由参数制作了本地副本并且在函数内可修改

#include <stdio.h>

void doSomething( int x )
{
    x = 42;
    printf( "The answer to Life, the Universe and Everything is (always): %i!\n", x );
}

int main( int argc, char **argv )
{
    int a = 0;
    doSomething( a );
    return -a;
}

现在这总是无错误地退出,但是在事物方案(内存空间)中,函数中表示为 x 的值保留在哪里?

我想应该(结合声明和定义)开始:

void doSomething( const int x )

我会让我的手腕被任何半体面的编译器拍打。

【问题讨论】:

  • 函数参数是传统上存储在call stack中的变量。
  • 注意:不存在称为C/C++ 的语言。在这两种语言中,此表达式都会调用未定义的行为。 :)
  • Aaarrrghh,没有人(在他们的头脑中)期望未定义的行为,但真的是这样吗?
  • @SouravGhosh 我不认为写入参数是未定义的行为。
  • @BlueWanderer: C/C++ 确实调用了未定义的行为。读取C 并对其进行修改是未排序的,并且如果标量对象上的副作用相对于同一标量对象上的不同副作用或使用相同标量对象的值的值计算是未排序的,则行为未定义。 ...(C 标准,6.5p2)。自己搜索 C++。

标签: c++ c parameters arguments parameter-passing


【解决方案1】:

对于函数doSomething()x 是函数的局部变量。它与在函数体开头定义的任何其他变量具有相似的范围。

一般来说,x 只存在于doSomething() 函数的范围内。 x 在调用 doSomething() 时定义(并传递参数),并在控件返回时销毁。只要函数调用正在执行(即变量仍在作用域内),参数就与任何其他变量相同,只是由函数调用中提供的参数初始化。

引用 C11,第 §6.2.1 章,标识符范围

[...] 如果声明符或类型说明符 声明标识符出现在块内或参数声明列表中 一个函数定义,标识符有块作用域,它终止于 关联块。 [...]

如您所知,x 是传递给函数调用的实际参数的本地副本,函数内部对x 所做的任何更改都不会反映 em> 进入调用者(实际参数),但只要函数内x 上的操作有效,编译器就没有理由抱怨。

【讨论】:

  • 但它作为变量存在,可以在函数内修改?
  • @SlySven 我已经编辑了答案以明确添加该信息,希望现在能更好地理解。
  • 是的 - 如果我打算修改在本地使用的值,我总是对传递的参数进行显式本地副本,但也许我过于偏执并且可以节省此类变量创建 假设我记得在函数中,在使用该变量时,它可能没有它开始时的值
  • 注意:一旦对象超出范围,C 不需要“破坏”;只是没有析构函数。 Inot 范围和标识符被终止(这是编译时的事情)。 C++ 作为不同的语言有不同的规则。
  • @SlySven:你为什么关心过早的优化?专注于编写人类可读代码。编译器很可能更清楚如何优化。
【解决方案2】:

函数的值参数实际上是函数的局部变量,除了它是在函数调用中初始化的。所以这些:

 void f( int n ) {
      n++;
   }

和:

 void g() {
      int n = 0;
      n++;
   }

实际上是相同的,如果对 f() 的调用是 f(0)。在这两种情况下,变量都会在函数退出时被丢弃。

【讨论】:

  • 我应该在函数调用中使用非零值,但我试图变得聪明并成功退出......
  • @Sly 用于初始化它的值并不重要。
【解决方案3】:

形参x是内存中与实参a分离的对象,因此对x的任何更改都不会反映在a中。

【讨论】:

  • 它甚至可能不在内存中(而只是在寄存器中),至少对于 AMD64 Linux API 上的整数和指针而言
  • 对于这样的问题,我不会参考实施细节。
【解决方案4】:

值 x 是本地的并保存在堆栈中。每个函数都有自己的堆栈部分,在程序进入函数时保留。 值 x 将位于堆栈的这一部分。它仅在定义它的功能范围内有效。 如果您更改 x,它将仅针对定义它的功能进行更改。 如果函数结束(从函数返回),则为函数保留的堆栈将被销毁,因此值 x 也将被销毁。所以 x 将无法访问,因为它不再存在。

【讨论】:

  • C 或 C++ 两种语言中的任何一种都没有堆栈。
  • @Olaf 函数参数是如何传递给函数的?它们被放置在堆栈上。你甚至可以检查反汇编代码,你会看到pushpop 指令是堆栈指令。
  • 请提供对需要堆栈来传递参数的标准(C 或 C++)的引用。还有许多其他方法和 C 实现,它们根本不使用堆栈来处理典型代码。注意:x86 是 C 编程使用最少的平台之一(而且很可能也不是 C++ 最常见的平台)。
  • 好的,我明白你的意思了。我认为这有助于更好地理解背景(在大多数情况下)发生的事情。在引擎盖下看到总是有帮助的。
  • 有些实现不使用堆栈。实际上,这几乎是所有 32 位和 64 位平台。
【解决方案5】:

正如其他人所解释的,x 在函数 doSomething 中是本地的,任何修改只会影响参数的本地副本。

但请注意,C++ 允许通过引用传递:doSomething() 的定义中的一个非常小的变化都会对该程序产生重大影响:

void doSomething( int& x ) {
    x = 42;
    printf( "The answer to Life, the Universe and Everything is (always): %i!\n", x );
}

通过上述函数定义,main 中的变量a 的值确实会更改为42。由于main() 中的代码是相同的,因此此 C++ 功能可能会导致代码混乱,尤其是对于 C 程序员而言。

【讨论】:

  • 猜猜,什么 - 我最初是一名 C 程序员,但我已经吸取了教训 - 这就是为什么如果通过引用传递并且你想向 doSomething 的调用者保证,应该使用 void doSomething( const int &amp;)它不会破坏给定的引用变量,并且编译器不需要/不必/不需要复制它... 8-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-15
  • 1970-01-01
  • 2014-04-06
  • 1970-01-01
  • 2014-03-08
  • 2018-07-21
相关资源
最近更新 更多