C语言逆向学习基础课 第7课 函数参数传递与返回值陷阱

张开发
2026/4/9 17:22:14 15 分钟阅读

分享文章

C语言逆向学习基础课 第7课 函数参数传递与返回值陷阱
文章目录一、函数参数传递与返回值陷阱1.1 课程目标1.2 核心知识点讲解1.2.1 函数参数传递的两种方式重点易错点1.2.2 函数返回值的高频陷阱重点规避方法1.3 实战示例综合错误排查1.4 课后作业实战巩固1.5 课程总结二、上一节课作业答案 switch与goto语句的正确使用2.1 实战作业代码2.2 代码功能说明2.3 注意事项一、函数参数传递与返回值陷阱1.1 课程目标明确函数参数传递的两种核心方式值传递、地址传递区分二者的使用场景与易错点掌握函数返回值的正确使用规范规避返回局部变量指针、返回值未校验等高频陷阱能独立排查并修正函数参数传递与返回值相关的代码错误提升代码健壮性。1.2 核心知识点讲解1.2.1 函数参数传递的两种方式重点易错点函数参数传递本质是“值拷贝”分为两种形式核心区别在于是否能修改实参的值也是实战中最易混淆的点。值传递pass by value定义将实参的值拷贝一份传递给形参形参是独立的局部变量修改形参的值不会影响实参。易错点误以为修改形参能改变实参导致逻辑错误。示例代码错误正确对比#includestdio.h// 错误示例试图通过值传递修改实参voidchangeValue(inta){a100;// 仅修改形参a实参不受影响}// 正确示例值传递仅用于获取实参值不修改实参intgetSum(inta,intb){returnab;// 仅使用实参的值返回计算结果}intmain(){intnum10;changeValue(num);printf(值传递后num %d\n,num);// 输出10实参未改变intsumgetSum(3,5);printf(35的和 %d\n,sum);// 输出8正确使用值传递return0;}地址传递pass by address定义将实参的地址指针传递给形参形参通过指针间接操作实参的内存空间修改指针指向的值会影响实参。易错点传递空指针、野指针指针解引用前未校验混淆“指针本身”与“指针指向的值”。示例代码正确用法易错规避#includestdio.h// 正确示例通过地址传递修改实参的值voidchangeValueByAddr(int*a){// 易错点规避先校验指针是否为空避免空指针解引用if(aNULL){printf(错误指针为空无法操作\n);return;}*a100;// 修改指针指向的实参值}intmain(){intnum10;changeValueByAddr(num);// 传递实参的地址printf(地址传递后num %d\n,num);// 输出100实参被修改// 易错案例传递空指针int*pNULL;changeValueByAddr(p);// 输出错误提示避免程序崩溃return0;}1.2.2 函数返回值的高频陷阱重点规避方法函数返回值的核心陷阱集中在“返回非法值”导致程序崩溃、结果异常以下是3个最高频陷阱及解决方案。陷阱1返回局部变量的指针栈内存释放问题原理函数内的局部变量存储在栈内存中函数执行结束后栈内存会被系统释放局部变量的地址变为“悬空地址”返回该地址会导致未定义行为程序崩溃、输出乱码。错误示例正确修正#includestdio.h#includestdlib.h// 错误示例返回局部变量的指针char*getStrError(){charstr[]hello world;// 局部变量栈内存存储returnstr;// 返回栈内存地址函数结束后地址失效}// 正确方案1使用静态局部变量存储在全局数据区函数结束后不释放char*getStrRight1(){staticcharstr[]hello world;// 静态局部变量returnstr;// 安全返回地址有效}// 正确方案2使用堆内存分配malloc手动管理内存char*getStrRight2(){char*str(char*)malloc(12);// 堆内存分配手动释放if(strNULL){// 规避陷阱校验malloc返回值避免空指针returnNULL;}strcpy(str,hello world);returnstr;}intmain(){// 错误调用输出乱码或程序崩溃char*p1getStrError();printf(错误返回值%s\n,p1);// 正确调用1char*p2getStrRight1();printf(正确返回值1%s\n,p2);// 正确调用2记得手动释放堆内存避免内存泄漏char*p3getStrRight2();if(p3!NULL){printf(正确返回值2%s\n,p3);free(p3);// 手动释放堆内存p3NULL;// 规避野指针}return0;}陷阱2返回值未校验忽略错误状态易错点调用有返回值的函数时不校验返回值是否合法如malloc返回NULL、文件操作返回-1直接使用返回值导致程序异常。规避方法调用函数后先校验返回值再进行后续操作参考上面getStrRight2的调用示例。陷阱3返回值类型与函数声明不一致易错点函数声明返回int类型但实际返回char、指针等其他类型编译器可能报警告运行时出现数据截断、行为异常。规避方法严格保证函数声明、定义、返回值类型一致。1.3 实战示例综合错误排查以下代码包含2个高频错误值传递误用、返回局部变量指针请排查并修正#includestdio.h// 错误1试图用值传递修改实参voidaddOne(inta){a;}// 错误2返回局部变量指针int*getArray(){intarr[5]{1,2,3,4,5};returnarr;}intmain(){intnum5;addOne(num);printf(num %d\n,num);// 预期输出6实际输出5int*arrgetArray();for(inti0;i5;i){printf(%d ,arr[i]);// 输出乱码}return0;}修正后代码#includestdio.h#includestdlib.h// 修正1使用地址传递修改实参voidaddOne(int*a){if(aNULL){return;}(*a);// 注意括号优先级问题}// 修正2使用堆内存分配返回数组int*getArray(){int*arr(int*)malloc(5*sizeof(int));if(arrNULL){returnNULL;}arr[0]1;arr[1]2;arr[2]3;arr[3]4;arr[4]5;returnarr;}intmain(){intnum5;addOne(num);printf(num %d\n,num);// 输出6int*arrgetArray();if(arr!NULL){for(inti0;i5;i){printf(%d ,arr[i]);// 输出1 2 3 4 5}free(arr);arrNULL;}return0;}1.4 课后作业实战巩固编写一个函数通过地址传递交换两个整数的值要求校验指针是否为空避免空指针解引用。编写一个函数返回一个长度为10的字符串内容为“0123456789”要求使用堆内存分配避免返回局部变量指针调用后手动释放内存。排查以下代码的错误至少2个并修正#includestdio.hchar*getString(){char*strhello;returnstr;}voidchangeNum(int*a,int*b){inttemp*a;*a*b;*btemp;}intmain(){char*pgetString();printf(%s\n,p);intx3,y5;changeNum(x,y);printf(x%d, y%d\n,x,y);return0;}1.5 课程总结参数传递值传递仅传递值不修改实参地址传递传递指针可修改实参核心是“指针解引用”操作。返回值陷阱禁止返回局部变量指针栈内存释放优先使用静态变量或堆内存调用函数必须校验返回值。核心原则指针使用前必校验非空堆内存分配后必释放返回值必校验避免野指针、空指针、内存泄漏。二、上一节课作业答案 switch与goto语句的正确使用2.1 实战作业代码#includestdio.h#includestdlib.h// 实战作业模拟简易计算器支持、-、*、/、退出规范使用switch合理使用goto清理资源intmain(){inta,b;charop;intflag1;// 控制循环int*p(int*)malloc(4);// 模拟需要清理的资源堆内存if(pNULL){printf(内存分配失败程序退出\n);return1;}*p0;// 初始化资源loop:// goto标签仅用于统一清理资源while(flag){printf(\n请输入运算式格式a op b如3 5输入q退出);// 读取输入校验输入合法性if(scanf(%d %c %d,a,op,b)!3){// 处理输入错误清空缓冲区while(getchar()!\n);printf(输入格式错误请重新输入\n);gotoloop;// 跳转至loop重新输入不清理资源}// 规范使用switch每个case加break避免穿透switch(op){case:printf(%d %d %d\n,a,b,ab);break;// 避免穿透到下一个casecase-:printf(%d - %d %d\n,a,b,a-b);break;case*:printf(%d * %d %d\n,a,b,a*b);break;case/:if(b0){printf(错误除数不能为0\n);gotoloop;// 重新输入}printf(%d / %d %d\n,a,b,a/b);break;caseq:flag0;// 退出循环break;default:printf(错误不支持的运算符\n);gotoloop;// 重新输入}}// 统一清理资源goto跳转至此避免资源泄漏cleanup:if(p!NULL){free(p);pNULL;}printf(程序正常退出资源已清理\n);return0;}2.2 代码功能说明本代码模拟简易计算器支持、-、*、/四则运算及退出功能。规范使用switch语句每个case后添加break避免穿透通过default处理非法运算符合理使用goto语句仅用于输入错误时重新获取输入、程序退出时统一清理堆内存资源。代码包含输入合法性校验、除数不为0校验、内存分配校验规避switch穿透、goto滥用、资源泄漏等陷阱逻辑清晰符合C语言实战规范。2.3 注意事项switch语句每个case后必须加break除非有明确的穿透需求本作业无穿透场景default必须添加处理非法输入避免程序异常。goto语句仅用于“统一清理资源”或“重新跳转至指定逻辑”禁止滥用如用于循环跳转、逻辑跳转避免代码可读性下降。资源管理堆内存分配后必须校验返回值程序退出前统一释放避免内存泄漏goto标签cleanup用于集中清理资源确保所有路径都能释放资源。输入处理输入格式错误时需清空缓冲区避免死循环除数为0时提示错误并重新输入提升程序健壮性。代码规范变量初始化、指针校验、注释清晰符合C语言实战开发习惯便于后续维护和排查错误。上一节课链接 C语言逆向学习基础课 第 6 课switch与goto语句的正确使用

更多文章