返回:安全开发篇(一)目录 · 上一篇:007、程序入口点 · 下一篇:009、控制台指令

对应视频:1.7 基础-函数调用过程.mp4

1、学习目标

这一节理解函数是如何被调用的。后续学习函数栈、调用约定、返回地址、栈溢出和逆向分析时,函数调用过程是绕不开的基础。

需要掌握:

  1. 函数声明;
  2. 函数定义;
  3. 函数调用;
  4. 参数和返回值;
  5. 调用完成后回到原位置继续执行。

2、函数基本写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

void print_banner(void)
{
printf("==== C Security Demo ====\n");
}

int add(int a, int b)
{
return a + b;
}

int main(void)
{
print_banner();

int result = add(3, 5);
printf("result = %d\n", result);

return 0;
}

3、调用流程

上面的程序可以按这个顺序理解:

步骤 说明
1 程序从 main 开始
2 执行到 print_banner();,跳到 print_banner 函数
3 print_banner 执行完,返回 main
4 执行到 add(3, 5),把参数传给 add
5 add 返回计算结果
6 main 输出结果并结束

函数调用的关键直觉是:执行流会临时跳到另一个函数,执行完后再回到调用位置。

4、声明、定义和调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int add(int a, int b); // 函数声明

int main(void)
{
int result = add(1, 2); // 函数调用
printf("%d\n", result);
return 0;
}

int add(int a, int b) // 函数定义
{
return a + b;
}
概念 含义
声明 告诉编译器有这个函数,参数和返回值长什么样
定义 函数真正的实现代码
调用 在某个位置使用这个函数

5、参数和返回值

参数是调用者传给函数的数据,返回值是函数处理后交回调用者的数据。

1
2
3
4
5
6
7
8
9
int max_value(int left, int right)
{
if (left > right)
{
return left;
}

return right;
}

调用:

1
int result = max_value(10, 20);

函数设计时要先说清楚:

  1. 需要哪些输入;
  2. 返回什么结果;
  3. 出错时怎么表示;
  4. 参数是否允许为空;
  5. 缓冲区长度是否需要一起传入。

6、安全开发观察

函数调用和安全的关系很深:

函数概念 安全连接
参数传递 输入是否可信、长度是否检查
返回值 是否判断错误状态
局部变量 栈空间、生命周期和越界风险
调用栈 逆向追踪和崩溃定位
返回地址 栈溢出分析基础

后续看到漏洞时,要经常问:

  1. 外部输入从哪个函数进入?
  2. 传参时有没有长度?
  3. 返回值有没有检查?
  4. 函数内部有没有数组或指针操作?
  5. 出错后执行流会不会继续往下走?

7、代码实验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>

int safe_add(int left, int right, int *out)
{
if (out == NULL)
{
return -1;
}

*out = left + right;
return 0;
}

int main(void)
{
int result = 0;

if (safe_add(3, 5, &result) != 0)
{
printf("add failed\n");
return 1;
}

printf("result = %d\n", result);
return 0;
}

这段代码提前引入一个安全习惯:函数通过返回值表示成功失败,指针参数使用前先判断是否为空。

8、复盘题

8.1 函数声明和函数定义有什么区别?

声明只告诉编译器函数的名字、参数和返回值;定义包含函数真正执行的代码。

8.2 函数调用结束后会发生什么?

函数执行完成后,程序会回到调用它的位置继续执行。如果函数有返回值,返回值会交给调用者使用。

8.3 为什么安全开发里要检查函数返回值?

因为很多函数会通过返回值表示成功或失败。如果不检查错误状态,程序可能在失败条件下继续执行,导致越界、空指针、权限绕过或数据错误。

这一节的核心:函数不是孤立代码块,而是执行流、参数、返回值和调用栈共同构成的行为单元。