做非经营网站需要营业执照哈尔滨市建设工程招标信息网
C++初学者指南第一步—14.函数调用机制
文章目录
- C++初学者指南第一步---14.函数调用机制
 - 1.记住:内存的结构
 - 2.函数调用是如何工作的
 - 3. 不要引用局部变量
 - 4. 常见编译器优化
 - 5. Inlining内联
 
1.记住:内存的结构

 堆(自由存储)
- 用于动态存储期对象,例如 std::vector 的内容。
 - 空间大,可以用于大容量存储(大多数用于主内存)。
 - 可以根据需要分配和释放任何对象。
 - 按照无特定顺序的分配(释放) ⇒ 碎片化。
 - 分配速度慢:需要为新对象找到连续未被占用的空间。
 

 栈
- 自动存储期对象使用:局部变量,函数参数等。
 - 空间小(通常只有几兆(M)字节)。
 - 分配速度快:新对象总是放在栈顶部。
 - 对象按它们创建的相反顺序被释放。
 - 无法释放顶端(= 最新的)以下的对象。
 
2.函数调用是如何工作的
<1>该示例假定没有编译器优化,例如内联(用函数体替换函数调用),返回类型优化等。
 此外,在函数调用时放入栈的确切顺序(调用约定)取决于平台(CPU 架构 + 操作系统 + 编译器)。
 
 <2>程序开始。
 
 <3>局部变量 y 被放入栈。
 
 <4> 局部变量 i 被放入栈。
 
 <5> 函数的返回值占位符被放入堆栈
 
 <6> 当前指令的内存地址被放在栈上,这样在离开被调用的函数后,我们就知道从哪里恢复程序。
 
 <7> 帧指针标记了当前函数的栈帧的开始。在当前栈帧内的一切都将被视为函数局部的。需要帧指针是因为不同的函数调用可能有不同大小的栈帧。
 
 <8> 执行跳转到函数square的内存地址。
 
 <9> 函数参数p放在栈上,它的值由调用参数(y的值)决定。
 注意:返回地址、占位符、局部参数等放在栈上的顺序取决于平台的调用约定(CPU体系结构+ OS +编译器)。
 
 <10> 函数局部变量 x 放到栈上。
 
<11> 表达式 p * p 的结果被赋给 x。
 
 <12> 语句return x,将 x 的值复制到 返回值占位符。
 
 <13>离开函数square时: 堆栈的顶部位置减少到堆栈帧下方;这意味着所有函数局部变量都从堆栈中弹出。
 
 <14> 执行通过跳转到之前存储的返回地址返回到调用位置。
 
 <15> 赋值语句int i = …会导致返回值被复制到i中。
 
 <16> square函数的返回值被从栈中弹出。
 
 <17> 局部变量k被放入栈。
 
 <18> 程序结束,所有关联的变量都会从栈中弹出。
 
3. 不要引用局部变量
如果我们把返回类型改为int&会怎么样呢?
 <1>
 
 <2> 在从square返回之前栈内容:
- 函数局部变量x
 - 函数参数p
 - 函数调用后的下一条指令的地址
 - square返回值的占位符
 - main函数的局部变量 y 和 i

<3> 语句return x;将 x 的地址复制到 返回值占位符。

<4> 离开函数square: 栈的顶部位置降低到栈帧下方; 这意味着所有square函数的局部变量都会从栈中弹出。
通过跳转到先前存储的返回地址,执行流程回到调用位置。

<5> 赋值 int& i = … 会导致返回值(一个整数的内存地址)被复制到引用 i& 中。
x的内存位置实际上在栈的当前顶部位置之上。任何后续的栈分配都会导致它被其他值覆盖。
这将导致 => 未定义行为
这样的程序在运行时行为是未定义的/非确定性的,因为它有时可能会工作(如果 x 的内存没有被覆写)有时可能不会。

 
4. 常见编译器优化
现代的 C++ 编译器进行多项优化(尤其是在较高的优化级别 -O2 和 -O3),使函数调用速度更快。
 Return Value Optimization 返回值优化 (RVO)
- 适用于类似:return Type{}; 或 return Type{argument,…}; 这样的语句。
 - 不会分配额外的占位符用于返回值,也不会进行复制。相反,外部对象 res 将直接在调用位置构造。
 - 这种优化是强制的,即在 C++17 版本中必定会执行。
 
Point foo (…) { …return Point{…};
}
Point res = foo();
 
Named Return Value Optimization 命名返回值优化 (NRVO)
- 适用于类似: return local_variable; 这样的语句。
 - 不会分配额外的占位符用于返回值,也不会进行复制。相反,本地对象 loc 和外部对象 res 被视为同一个对象。这样在调用点仅会发生一次分配。
 - 这种优化不是必需的,但几乎所有现代编译器都会尽可能地执行它。
 
Point foo (…) {Point loc;…return loc;
}
Point res = foo();
 
5. Inlining内联
调用小/短函数的地方被该函数的代码替换。
 
 内联只会发生在编译器“看到”函数声明的同时也看到它的完整定义,如果我们分别编译程序的不同部分,这种情况就不一定会发生(更多内容请参考《分离编译》章节)。
 这是 C++ 性能优势的一个来源。在许多其他语言(比如 Java、C# 等)中,内联化要困难得多,有时甚至是不可能的。这些语言通常具有始终开启的多态性,这意味着所有/大多数函数/方法调用只能在运行时解析。
附上原文链接
 如果文章对您有用,请随手点个赞,谢谢!^_^
