C++栈的生长方向和函数传参压栈方式

本文详细解析了C++中的两种主要调用约定:__cdecl 和 __stdcall 的工作原理及区别。通过具体代码示例展示了参数如何被压入栈中以及栈的清理方式,帮助读者理解不同调用约定的特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include<stdio.h>
#include"stdlib.h"
#include<iostream>
using namespace std;
//58
void __cdecl func3(int i, char *szTest)
{
cout << "szTest的地址:" << &szTest << endl;
cout << "sz本身的值:" << (void*)szTest << endl;
cout << "i的地址:" << &i << endl;

int k,k2;
cout << "k的地址:" << &k << endl;
cout << "k2的地址:" << &k2 << endl;
cout << "-------------------------------------------------------" << endl;
}

void __stdcall func4(int i, char *szTest)
{
cout << "szTest的地址:" << &szTest << endl;
cout << "sz本身的值:" << (void*)szTest << endl;
cout << "i的地址:" << &i << endl;

int k,k2;
cout << "k的地址:" << &k << endl;
cout << "k2的地址:" << &k2 << endl;
}
int main()
{
int a[2];
cout << &a[0] << endl;
cout << &a[1] << endl << endl;
int i = 0x22;
char szTest[4] = {'a','b', 'c', 'd'};
cout << &i << endl;
cout << (void*)szTest << endl;
cout << *szTest << endl;

func3(i, szTest);
func4(i, szTest);
}

输出:

0012FE98   //这是a[0]地址
0012FE9C   //这是a[1]的地址。

0012FE8C   
0012FE80
a
szTest的地址:0012FDAC //从这个地址和i的地址比较,可知seTest先入栈的。
sz本身的值:0012FE80
i的地址:0012FDA8
k的地址:0012FD98
k2的地址:0012FD8C
-------------------------------------------------------
szTest的地址:0012FDAC   //采用__stdcall的函数调用,参数也是从右到左进栈的。
sz本身的值:0012FE80
i的地址:0012FDA8
k的地址:0012FD98
k2的地址:0012FD8C

结论:栈的生长方向是从大到小。

           __cdecl:表示函数的参数是从右到左入栈。

注意:

1)_cdecl是C语言采用的默认调用方法,它是由主调用函数进行参数压栈并且恢复堆栈。它的优点是支持printf这样的可变参数调用。(既然由主调用函数负责压栈和恢复堆栈,主调函数知道参数的个数,所以可以支持printf那样的可变参数调用。)每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。 

2)_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。

3) 注意,Windows API默认的函数调用方式是__stdcall。所以有这个定义,#define WINAPI __stdcall。

    而MFC,C/C++默认调用方式是__cdecl。


转自:http://hi.baidu.com/lieyu063/item/2ea9a2792527163c714423ad

<think>嗯,用户之前已经问过关于C++函数传参、作用域内联函数的总结,现在他们想要更详细的教程。我需要结合之前的回答提供的引用内容,进一步扩展每个部分,确保涵盖更多细节示例。 首先,函数传参部分。用户已经了解值传递、指针传递引用传递的基本概念,但可能需要更深的解释,比如不同传参方式的性能影响,或者何时选择哪一种。例如,引用传递对于大型对象的效率优势,可以结合引用中的内联函数部分,说明为什么引用适合内联一起使用。同时,引用[2]提到默认形参值的函数,这部分在之前的回答中没有涉及,可能需要补充到传参机制里,作为默认参数的使用方法注意事项。 接下来是作用域规则。之前的回答提到了局部变量、全局变量命名空间,但用户可能需要更多关于作用域嵌套、静态局部变量的信息。例如,静态局部变量在函数调用间保持值的特性,这在引用中没有直接提到,但属于作用域的一部分,应该加。另外,命名空间的作用域可以进一步举例说明,比如如何在不同命名空间中定义相同名称的变量,以及如何使用using声明或指令。 然后是内联函数。根据引用[1][3],内联函数在编译时替换函数体,减少调用开销。需要详细说明内联函数的优缺点,比如代码膨胀的风险,以及编译器如何处理复杂函数的内联请求。引用[2]提到内联函数应该放在头文件中,这一点需要强调,并解释为什么这样做。此外,内联函数与宏的区别,比如类型安全调试能力,可以结合引用中的例子进一步展开。另外,引用[4]提到函数模板内联的关系,可能需要简要说明模板与内联的协同使用,但用户的问题主要集中在传参、作用域内联,所以这部分可能不需要深。 用户可能还希望看到更多的代码示例,特别是结合不同传参方式的对比,比如值传递引用传递在性能上的差异。此外,关于作用域,可以增加关于块作用域的例子,比如在循环或条件语句中定义的变量。对于内联函数,可以展示一个实际的内联替换过程,如引用[1]中的例子,说明编译器如何替换函数调用。 另外,用户提供的引用中有提到递归函数无法内联,这一点需要在适用条件中明确说明。同时,引用[2]指出即使没有inline关键字,编译器也可能内联函数,需要解释现代编译器的优化策略,让用户了解inline关键字只是一个建议,并非强制。 最后,整理关键点,确保结构清晰,分章节详细讲解,每个部分都有足够的示例注意事项。可能还需要添加常见问题,比如何时使用指针传递而非引用,或者全局变量的替代方案,以帮助用户避免常见错误。总结部分用表格对比不同传参方式,以及内联函数的优缺点,可以增强记忆点。</think>--- ### C++ 函数传参、作用域与内联函数详细教程 --- #### 一、函数传参机制详解 C++ 支持三种传参方式:**值传递**、**指针传递****引用传递**。不同方式对性能、内存代码安全性有直接影响。 1. **值传递** - **原理**:创建实参的副本传递给形参,函数内操作不影响原变量。 - **适用场景**:小型数据(如 `int`, `char`)且无需修改原值。 - **示例**: ```cpp void modify(int a) { a = 100; } // 修改副本,原变量不变 int main() { int x = 10; modify(x); // x 仍为 10 return 0; } ``` 2. **指针传递** - **原理**:传递变量的内存地址,通过解引用操作修改变量值。 - **特点**:显式操作地址,需处理空指针野指针风险[^3]。 - **示例**: ```cpp void swap(int* a, int* b) { int tmp = *a; *a = *b; *b = tmp; } int main() { int x = 1, y = 2; swap(&x, &y); // x=2, y=1 return 0; } ``` 3. **引用传递** - **原理**:通过引用别名直接操作原变量,语法简洁且避免指针复杂性。 - **优势**:适合传递大型对象(如类实例),避免拷贝开销[^3]。 - **示例**: ```cpp void increment(int& num) { num++; } int main() { int x = 5; increment(x); // x=6 return 0; } ``` 4. **默认参数**(补充自引用[^2]) - 允许为函数参数设置默认值,调用时可省略对应实参。 - **规则**:默认参数必须从右向左连续定义。 - **示例**: ```cpp void print(int a, int b = 10) { cout << a << ", " << b << endl; } int main() { print(5); // 输出:5, 10 print(5, 20); // 输出:5, 20 return 0; } ``` --- #### 二、作用域规则深解析 变量作用域决定了其可见性生命周期,直接影响程序的内存管理逻辑正确性。 1. **局部作用域** - **定义**:在函数或代码块(如 `{}`)内部声明的变量。 - **生命周期**:从声明处到代码块结束。 - **示例**: ```cpp void func() { int a = 10; // 局部变量 if (true) { int b = 20; // 仅在 if 块内有效 } // cout << b; // 错误:b 不可见 } ``` 2. **全局作用域** - **定义**:在函数外声明的变量,整个程序可见。 - **风险**:过度使用易导致命名冲突不可控修改。 - **示例**: ```cpp int global = 100; // 全局变量 void func() { global++; // 可修改全局变量 } ``` 3. **命名空间作用域** - **作用**:隔离同名变量/函数,解决全局作用域冲突。 - **示例**: ```cpp namespace A { int var = 1; } namespace B { int var = 2; } int main() { cout << A::var << ", " << B::var << endl; // 输出:1, 2 return 0; } ``` 4. **静态局部变量** - **特性**:生命周期贯穿整个程序运行期,但作用域仍为局部。 - **示例**: ```cpp void counter() { static int count = 0; // 仅初始化一次 count++; cout << count << endl; } int main() { counter(); // 1 counter(); // 2 return 0; } ``` --- #### 三、内联函数全面剖析 内联函数通过代码展开消除函数调用开销,但需谨慎使用以避免代码膨胀。 1. **核心原理** - **编译期展开**:编译器将函数体直接嵌调用位置,省去、跳转等操作[^1][^3]。 - **示例**: ```cpp inline int square(int x) { return x * x; } int main() { int result = square(5); // 编译后等价于 int result = 5 * 5; return 0; } ``` 2. **适用场景** - **短小函数**:如简单计算、条件判断(参考引用[^2])。 - **高频调用**:如循环中的工具函数。 - **禁用场景**: - 递归函数(无法展开)。 - 含复杂逻辑(如循环、多分支)。 3. **与宏的对比** - **安全性**:内联函数支持类型检查,宏仅为文本替换。 - **示例对比**: ```cpp #define MACRO_SQUARE(x) ((x) * (x)) // 宏可能引发副作用 inline int inline_square(int x) { return x * x; } // 安全 ``` 4. **现代编译器优化** - 即使未使用 `inline` 关键字,编译器可能自动内联简单函数。 - 复杂函数即使标记为 `inline`,也可能被忽略(引用[^2])。 5. **头文件中的内联函数** - **必要性**:内联函数定义需在头文件中,确保编译器在调用处可见[^2]。 - **示例**: ```cpp // header.h inline double circleArea(double r) { return 3.14159 * r * r; } ``` --- #### 四、关键总结与对比 | **特性** | 值传递 | 引用传递 | 内联函数 | |-------------------|------------------|------------------|------------------------| | **内存操作** | 拷贝副本 | 直接操作原变量 | 无额外内存操作 | | **适用场景** | 小型数据 | 大型对象/需修改 | 短小高频函数 | | **性能开销** | 高(拷贝成本) | 低 | 低(无调用开销) | --- #### 五、常见问题解答(Q&A) 1. **如何选择指针传递或引用传递?** - 指针传递:需显式传递地址,适合需要 `nullptr` 的场景(如可选参数)。 - 引用传递:语法简洁,强制绑定有效对象,适合大多数场景[^3]。 2. **内联函数会导致代码膨胀吗?** - 是。若函数体较大或多处调用,可能显著增加代码体积(引用[^2])。 3. **全局变量如何避免命名冲突?** - 使用命名空间或静态全局变量(`static` 限制作用域为当前文件)。 --- #### 六、综合代码示例 ```cpp #include <iostream> using namespace std; // 全局变量 int global = 100; // 引用传递修改值 void updateByRef(int& num) { num += 10; } // 内联函数计算立方 inline int cube(int x) { return x * x * x; } int main() { int a = 5; updateByRef(a); // a = 15 int result = cube(a); // 展开为 15*15*15 cout << "Result: " << result << endl; // 输出 3375 return 0; } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值