性质一: 在C语言中,函数传参是值传递,传递的是实参的副本(值的拷贝),而非实参本身。若需通过函数修改外部变量,需传递变量的地址(指针),本质仍是对地址值的拷贝,通过指针间接访问并修改原变量。
#include
void foo(int n)
{
n += 10;
}
int main(void)
{
int i = 20;
foo(i);
printf("%d\n",i);
return 0;
}
该程序输出结果为30,因为在C语言中,函数参数的传递是值传递。
性质二:在C语言中,函数参数默认是自右向左传参。
#include
int add(int a,int b)
{
return a + b;
}
int foo(int n)
{
printf("%d\n",n);
return n;
}
int main(void)
{
int i = 20;
int j = 10;
printf("%d\n",add(foo(i),foo(j)));
return 0;
}
程序输出结果为10 20 30,说明函数参数默认是自右向左传参。
degug: gcc 20250724_1.c -g
release:
函数定义的本质
c语言中的表达式都是指令集。
函数的入口地址
程序计数器(Program Counter,简称PC) 是计算机处理器中的一个重要寄存器,主要功能是存储下一条要执行的指令在内存中的地址。
其工作原理可简单理解为:
1. CPU从PC指向的内存地址读取当前要执行的指令。
2. 指令执行完成后,PC会自动更新,指向紧接着的下一条指令的地址。
3. 当遇到跳转、分支(如if-else)、循环等指令时,PC会被设置为跳转目标的指令地址,从而改变程序的执行顺序。
#include
int add(int a,int b)
{
int ret a + b;
return ret;
}
int main(void)
{
int sum;
sum = add(10,20);
printf("%d\n",sum);
return 0;
}
栈顶(低地址) | ret |
a | |
b | |
sum | |
main | |
printf返回地址 | |
10 | |
栈底(高地址) | 20 |
栈区(Stack) 是计算机内存中用于临时存储数据的一块区域,主要用于支持程序的函数调用、局部变量存储等操作,遵循“先进后出”的原则。
1. 内存分配(入栈)
当程序执行到函数调用时,编译器会自动完成栈区的内存分配。
步骤大致为:(1)首先将函数的返回地址(即函数执行结束后要回到的原程序位置)压入栈中,确保函数执行完能正确返回。(2)接着将函数的参数按一定顺序(默认自右向左)压入栈中。(3)然后为函数内的局部变量分配栈空间,压入栈中。
2. 内存释放(出栈)
当函数执行结束时,栈区的内存会自动释放。
步骤为:(1)函数的局部变量、参数等数据按“后入先出”的顺序依次从出栈,并回收这些空间。(2) 最后出栈之前的返回地址,程序根据该地址跳回原调用位置继续执行。
注意:栈区的大小在程序运行前通常是固定的,如果分配的数据超过栈区容量,会导致“栈溢出”错误(如递归调用过深)。
2. 堆区(heap)
3. 全局区(静态区):用于存放全局变量和静态变量。
4. 文字常量区:用于存放常量字符串
5. 代码区:存放函数体的二进制代码
函数的递归调用是指函数在执行过程中直接或间接调用自身的一种编程方式。
1.直接递归调用
例如,计算阶乘
1 #include
2
3 int fm(int m)
4 {
5 if(1 == m)
6 {
7 return 1;
8 }
9 else
10 {
11 return fm(m - 1) * m;
12 }
13 }
14
15 int main(void)
16 {
17 printf("%d\n",fm(4));
18 return 0;
19 }
例如,计算斐波那契数列前10项的和
22 int fn(int n)
23 {
24 if(1 == n || 2 == n)
25 {
26 return 1;
27 }
28 else
29 {
30 return fn(n - 1) + fn(n - 2);
31 }
32 }
33
34 int main(void)
35 {
36 printf("%d\n",fn(10));
37 return 0;
38 }
2.间接递归调用
终止条件:必须存在一个或多个不需要递归就能解决的情况,否则会导致无限递归。