- 公开视频 -> 链接点击跳转公开课程
- 博客首页 -> 链接点击跳转博客主页
函数定义
-
函数的概念
-
函数是一段执行特定任务的代码块,具有名称和可选的参数和返回值。
-
函数可以将代码模块化,提高代码的可读性、可维护性和可重用性。
-
-
函数的组成
-
返回类型 函数名(形参列表) { }
-
返回值 -> 函数返回值类型可在函数体内部通过return语句返回对应类型
-
调用约定 -> __cdecl __stdcall __fastcall
-
函数名 -> 用户定义的函数名类似于变量标识符,遵守标识符的命名规则
-
参数 -> 各类型的参数集合,通过逗号分隔,在定义函数时指定的形参(未进行函数调用时不占用内存空间因此也称为形式参数或虚拟参数且不能赋值),格式为类型 名称, 类型 名称 ... 参数是可有可无的并不是必须存在
-
函数体 -> 函数内代码放在{}内
-
return -> 函数体内部执行到return语句则会返回调用位置执行剩余代码,return语句后尽量与函数定义返回值类型保持一直且存在返回值类型时必须在return语句后写对应的值
-
-
函数的声明和定义
-
函数的声明是指在使用函数之前提供函数的原型,包括函数的名称、参数类型和返回类型。
-
函数的定义是指实现函数的代码块,包括函数的名称、参数列表、函数体和返回语句。
-
// 函数的声明 int add(int a, int b); // 函数的定义 int add(int a, int b) { return a + b; }
-
-
函数的参数传递
-
函数可以接受参数,参数是函数执行时传递给函数的值。
-
参数可以是基本数据类型、指针、数组或结构体等。
-
// 基本数据类型参数 int square(int num); // 指针参数 void swap(int *a, int *b); // 数组参数 int sum(int arr[], int size); // 结构体参数 void printStudent(struct Student s);
-
-
函数的返回值
-
函数可以返回一个值,表示函数执行的结果。
-
返回值可以是基本数据类型、指针、结构体或联合体等。
-
// 返回基本数据类型 int add(int a, int b); // 返回指针 int* createArray(int size); // 返回结构体 struct Point getMidPoint(struct Point p1, struct Point p2);
-
函数调用
- 语法:函数名(参数);
- 无参无返
- 有参无返
- 无参有返
- 有参有返
控制台入口函数
-
控制台程序入口函数
-
#include <stdio.h> #include <stdlib.h> int main() { printf("Hello 0xCC \r\n"); return 0; }
-
-
函数参数
-
反汇编分析入口函数参数
-
F11调试运行程序
-
切换到反汇编界面
-
堆栈栈顶 -> ESP -> 返回地址(调用位置)
-
参数传递
-
函数调用
-
平衡堆栈
-
-
参数含义
-
argc -> 命令行参数个数
-
argv -> 命令行参数
-
envp -> 环境变量
-
#include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[], char* envp[]) { printf("argc -> %d \r\n", argc); do { printf("argv -> %s\r\n", *argv); } while (*(++argv)); do { printf("envp -> %s\r\n", *envp); } while (*(++envp)); return 0; }
-
-
入口函数
-
C/C++控制台程序的入口函数main,并不是程序执行的第一条指令
-
Visual Stuidio 2019 x86 Debug
-
定位main / 回溯main
-
IDA
-
将编译好的文件拖入到IDA当中解析
-
在Functions窗口中搜索main(存在符号文件)
-
-
DBG
-
将编译好的文件拖入到DBG当中解析(入口断点)
-
反汇编窗口右键搜索-当前模块-字符串
-
转到main函数头部单击第一行指令并且按下F2下断点
-
-
-
-
函数声明及定义
-
函数声明
-
函数声明是在使用函数之前提供函数的原型,以便编译器知道函数的名称、参数类型和返回类型。
-
函数声明格式 -> 返回类型 函数名(参数列表);
-
函数声明作用
- 向编译器提供函数的原型,以便编译器在使用函数之前知道函数的存在。
- 帮助编译器检查函数的调用是否正确,包括参数的类型和数量是否匹配。
-
-
函数定义
- 函数定义是实现函数的具体代码,并指定函数在被调用时应该执行的操作。
- 函数定义格式 -> 返回类型 函数名(参数列表) { // 函数体 // 执行操作的代码 // 返回值(如果有)}
- 函数定义包括函数的返回类型、函数名、参数列表和函数体。函数体是函数的具体实现,包含执行操作的代码。
-
课堂代码
#include <stdio.h> #include <stdlib.h> //函数声明 int Add(int a, int b); int main() { //函数调用 int ret = Add(12, 6); printf("%d \r\n", ret); return 0; } //函数定义 int Add(int a, int b) { int c = a + b; return c; }
-
函数声明提供函数的原型,包括函数名、参数列表和返回类型。
-
函数定义实现函数的具体代码,包括函数名、参数列表、返回类型和函数体。
-
函数声明和定义的目的是提供函数的接口和实现,以便在程序中使用和调用函数。
-
函数声明和定义需要保持一致,包括函数名、参数列表和返回类型。
-
函数声明允许在使用函数之前提供函数的原型,以便编译器检查函数调用的正确性。
函数分文件编写
-
函数分文件编写是将程序中的函数分散到多个独立的源文件中,每个文件包含一个或多个相关的函数。
- 模块化:将函数分开存放,使代码结构更清晰,易于理解和管理。
- 可维护性:每个文件只包含特定功能的函数,当需要修改某个特定功能时,只需修改对应的文件,而不影响其他部分。
- 可重用性:可以将一些常用的函数定义为库函数,供其他程序重复使用。
-
函数分文件编写的步骤
- 创建头文件(.h文件):头文件包含函数的声明,用于在其他文件中引用函数。头文件中通常包含函数的原型、宏定义、结构体定义等。
- 创建源文件(.c文件):源文件包含函数的具体实现,即函数的定义。每个源文件可以包含一个或多个函数的定义。
- 在需要使用函数的源文件中引入头文件:使用#include预处理指令将头文件引入到需要使用函数的源文件中。
-
课程示例
-
头文件
#pragma once //函数声明 int Add(int a, int b);
-
源文件
#include "Cacl.h" int Add(int a, int b) { return a + b; }
-
调用
#include <stdio.h> #include <stdlib.h> #include "Cacl.h" int main() { //函数调用 int ret = Add(12, 6); printf("%d \r\n", ret); return 0; }
-
函数参数传递
-
值传递
-
函数值传递是指将参数的值复制一份,然后将复制的值传递给函数。在函数内部,对参数的修改不会影响原始的参数值。
-
代码示例
#include <stdio.h> void modifyValue(int x) { x = x * 2; printf("Inside function: %d\n", x); } int main() { int num = 5; printf("Before function: %d\n", num); modifyValue(num); printf("After function: %d\n", num); return 0; }
-
函数的参数是在栈上分配内存的,函数调用结束后,栈上的内存会被释放,不会影响原始参数的值。
-
-
地址传递
#include <stdio.h> void modifyval(int* p) { //p -> main.num.addr *p = 100; printf("Fun -> %d\r\n", *p); } int main() { int num = 10; printf("Main -> %d\r\n", num); modifyval(&num); printf("Main -> %d\r\n", num); return 0; } #include <stdio.h> void Fun(int* p) { for (size_t i = 0; i < 5; i++) { p[i] = 10 + i; } } int main() { int Arr[5] = { 0 }; for (size_t i = 0; i < 5; i++) { printf("Arr[%d] -> %d\t", i, Arr[i]); } printf("\n"); Fun(Arr); for (size_t i = 0; i < 5; i++) { printf("Arr[%d] -> %d\t", i, Arr[i]); } return 0; }
函数调用约定
-
标准调用约定(CDECL) -> 在标准调用约定下,函数的参数从右向左依次入栈,由调用者负责清理栈空间。返回值通常通过寄存器传递。
- C语言库函数使用__cdecl
-
/* _Check_return_ int __cdecl strcmp( _In_z_ char const* _Str1, _In_z_ char const* _Str2 ); */
-
微软调用约定(STDCALL) -> 微软调用约定是用于Windows平台的一种调用约定,函数的参数从右向左依次入栈,由被调用函数负责清理栈空间。
- WIN32API使用__stdcall
-
/* #define WINAPI __stdcall int WINAPI MessageBoxA( _In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType); */
-
快速调用约定(FASTCALL) -> 快速调用约定在标准调用约定的基础上做了优化,将部分参数通过寄存器传递,减少了栈操作的开销。
- x86下自己指定函数调用约定且x64下默认使用__fastcall
-
void __fastcall Fun(int a, int b, int c, int d) { }
-
调用约定 参数压栈顺序 平衡堆栈 __cdecl 从右至左入栈 调用者清理栈 __stdcall 从右至左入栈 自身清理堆栈 __fastcall ECX(PARAM1)/EDX(PARAM2) 剩下->从右至左入栈 自身清理堆栈 -
__cedcl
#include <stdio.h> #include <stdlib.h> void __cdecl Fun(int a, int b, int c, int d) { } int main() { Fun(1, 2, 3, 4); return 0; } push 4 push 3 push 2 push 1 call Fun (044F922h) add esp,10h
-
__stdcall
#include <stdio.h> #include <stdlib.h> void __stdcall Fun(int a, int b, int c, int d) { } int main() { Fun(1, 2, 3, 4); return 0; } main push 4 push 3 push 2 push 1 call Fun (045037Ch) Fun ret 10h
-
__fastcall
-
#include <stdio.h> #include <stdlib.h> void __fastcall Fun(int a, int b, int c, int d) { } int main() { Fun(1, 2, 3, 4); return 0; } main push 4 push 3 mov edx,2 mov ecx,1 call Fun (0450381h) Fun ret 8
-
宏函数
-
宏函数是一种在编译期间进行文本替换的机制,它可以用来定义常量、简化代码、提高代码的可读性和可维护性等。
-
宏定义的基本语法 -> #define 宏名称 替换文本
-
宏定义的注意事项
- 宏定义不需要分号结尾
- 宏定义中的替换文本不进行类型检查,因此需要谨慎使用
- 使用宏定义时,替换文本中的参数应该使用括号括起来,以避免优先级问题
- 宏定义在预处理阶段进行文本替换,因此它没有作用域的概念,可以在任何地方使用
-
宏定义的课堂示例
#include <stdio.h> #include <stdlib.h> //宏函数 #define ADD(A,B) ((A) + (B)) int __cdecl Add(int a, int b) { return a + b; } int main() { int num1 = Add(123, 123); int num2 = Add(112323, 123); printf("%d %d \r\n", num1, num2); int num3 = ADD(123, 123); int num4 = ADD(112323, 123); printf("%d %d \r\n", num3, num4); return 0; }
函数参数类型
-
数组
-
数组参数
#include <stdio.h> void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {1, 2, 3, 4, 5}; int size = sizeof(arr) / sizeof(arr[0]); printArray(arr, size); return 0; }
-
数组返回
#include <stdio.h> int* createArray(int size) { int* arr = malloc(size * sizeof(int)); for (int i = 0; i < size; i++) { arr[i] = i + 1; } return arr; } int main() { int size = 5; int* arr = createArray(size); for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); free(arr); return 0; }
-
-
结构体
-
结构体普通参数
#include <stdio.h> #include <stdlib.h> struct Student { int m_Id; int m_Score; }; void Fun1(struct Student student) { student.m_Id = 1234; student.m_Score = 60; printf("Id -> %d Score -> %d \r\n", student.m_Id, student.m_Score); } int main() { struct Student s1 = {9527, 99}; printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score); Fun1(s1); printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score); return 0; }
-
结构体指针参数
#include <stdio.h> #include <stdlib.h> struct Student { int m_Id; int m_Score; }; void Fun1(struct Student* student) { student->m_Id = 1234; student->m_Score = 60; printf("Id -> %d Score -> %d \r\n", student->m_Id, student->m_Score); } int main() { struct Student s1 = {9527, 99}; printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score); Fun1(&s1); printf("Id -> %d Score -> %d \r\n", s1.m_Id, s1.m_Score); return 0; }
-
结构体数组参数
#include <stdio.h> #include <stdlib.h> struct Student { int m_Id; int m_Score; }; void Fun1(struct Student Arr[], int nSize) { for (size_t i = 0; i < nSize; i++) { Arr[i].m_Id = (i + 1) * 10; Arr[i].m_Score = (i + 1) * 20; } } int main() { struct Student Arr[5] = { 0 }; Fun1(Arr, 5); for (size_t i = 0; i < 5; i++) { printf("Index -> [%d] Id -> [%d] Score -> [%d] \r\n", i, Arr[i].m_Id, Arr[i].m_Score); } return 0; }
-
-
不定量参数
-
函数框架
-
#include <stdarg.h> return_type function_name(type fixed_param, ...) { va_list args; // 初始化可变参数列表 va_start(args, fixed_param); // 访问可变参数 // 在函数内部,使用va_arg宏来读取参数值 // 结束可变参数列表的访问 va_end(args); // 函数体 }
- return_type - 函数的返回类型
- function_name - 函数的名称
- fixed_param - 固定参数
- type - 固定参数的类型
- ... - 可变数量的参数
-
课堂示例
#include <stdio.h> #include <stdarg.h> void PrintIntegers(int count, ...) { va_list args; va_start(args, count); for (int i = 0; i < count; i++) { int value = va_arg(args, int); printf("%d ", value); } va_end(args); } int main() { PrintIntegers(3, 1, 2, 3); // 输出:1 2 3 return 0; }
-
课堂练习
#include <stdio.h> #include <stdarg.h> int MyPrintf(const char* format, ...) { va_list args; va_start(args, format); int count = 0; // 记录输出字符的数量 for (int i = 0; format[i] != '\0'; i++) { if (format[i] == '%') { i++; // 跳过 '%' 字符 // 根据格式字符读取对应类型的参数值 switch (format[i]) { case 'd': count += printf("%d", va_arg(args, int)); break; case 'f': count += printf("%.2f", va_arg(args, double)); break; case 's': count += printf("%s", va_arg(args, char*)); break; default: putchar(format[i]); count++; break; } } else { putchar(format[i]); count++; } } va_end(args); return count; }
-