深入理解C语言的函数参数
1、一个简单的函数
int Add(int x, int y)
{return x + y;
}int main()
{printf("%d", Add(2, 3, 4, 5, 6));return 0;
}
这一段足够简单的代码,闭眼都能知道运行结果会在屏幕上打印 5 。那编译器是怎么处理后面的 4、5、6 ?
我们再看看这个函数。
void MyTest(int a, int b, int c, int d)
{printf("%d", a);
}
似乎参数 b、c、d 的设定是多余的。不论这三个参数传入什么值,都不影响结果。那上述的 Add 函数是不是也能看作后续 4、5、6 对应的参数没有用到,所以没有表现出任何现象?
带着这个问题,再看一个函数:
void MyTest_2(int num)
{int* ptr = #for (int i = 0; i <= num; i++){printf("%d ", *ptr);ptr++;}
}
是不是有点懵?对形参取地址是个什么操作?还要对形参的指针进行移动再打印出来又是什么鬼?预感上,大概率会报非法访问。
那不妨,我们在 main 函数里调用一下?
MyTest_2(0);
MyTest_2(1);
MyTest_2(10);
然而结果是:

虽然返回了一堆不明所以的值,但返回代码 0 说明程序压根就没有报错。而参数 0 、 1 、 10 都被完整打印出来了,简直是毁三观。
知道你很急,但是你先别急,再在 main 函数中试试这个足以让人懵逼的例子:
MyTest_2(5, 100, 20, 35, 40, 114514);
结果更毁三观了:

什么玩意?明明 MyTest 创建时只设定了一个参数,为什么传入六个参数能全部打印出来?是不是说明,一开始的 Add 调用,后面的 4、5、6 也一并进行了传参,只是没有在函数内部进行使用?
要弄懂这个问题,首先得了解编译器对函数调用时的参数是怎么处理,传参过程又是怎么样的。
2、函数传参过程
2.1、栈帧建立之前
调用函数时,系统会在内存中创建对应函数的栈帧。关于栈帧建立及销毁这部分内容可以看这一篇开头部分:函数栈帧简述。
而在进行栈帧建立之前,程序还会执行一系列的操作。以这段代码为例,直接在汇编中看看 ret 赋值时的 Add 调用,汇编指令到底做了什么:
int Add(int x, int y)
{return x + y;
}int main()
{int ret = Add(0xAB, 0xCD, 0xEF, 0xAA, 0xDD);return 0;
}

当前栈帧是 main 函数,根据以上汇编指令,在调用 Add 函数之前,程序将 0xAB、0xCD、0xEF、0xAA、 0xDD 这五个参数逆序放入 main 函数栈顶( ESP 是栈顶寄存器)。

这一步实际上就是函数传参。通过这一步得出结论,不论函数在创建时定义了多少个形参,甚至不定义形参,只要在调用时,函数名后的括号内写入参数,就一定会进行传参。
2.2、参数调用

上述两句汇编代码首先是将 ebp+8 位置的值存入 eax 寄存器,再让 eax 寄存器中的值 += ebp+12 位置的值。而 ebp+8 的地址与 ebp+12 的地址分别储存了 0xAB 和 0xCD 。

至此就是一次完整的传参及参数调用。
也就是说,只需要知道第一个参数的地址,那么剩下的参数即使不在创建函数时定义,也可以通过第一个参数的地址进行访问。就此,最开始的 MyTest_2 函数产生的现象也就解释完毕。
3、可变参数列表
3.1、定义阐述
严格来说 C 语言的函数参数数量并不是固定的,那么在应用上根据传入的各个参数类型及第一个参数的地址,对函数传入任意参数个数,只需要通过某种方式在函数内部进行调用,那么函数的灵活性和扩展性就大大提高了。
很好, printf 也是这么想的。在使用 printf 时,第一个参数中有几个占位符,后续就带几个参数,各位对这规则应该已经形成肌肉记忆了。而对于之前的 MyTest_2 函数,唯一定义的参数便是后续传入有效参数的个数。而为了语义上更加直观,像这类可对后续参数进行操作的函数在创建时,一般会加上三个点。当然,也是为了语义,将变量名改为 argc (argument count):
void MyTest_2(int argc, ...)
{int* ptr = &argc;for (int i = 0; i <= argc; i++){printf("%d ", *ptr);ptr++;}
}
如以上函数中用其中某个参数确定后续参数的个数,那么这一系列参数就叫可变参数列表。
3.2、初步实现
虽然在 MyTest_2 中已经初步实现了带可变参数列表的函数创建,但这个函数好像没什么用。所以这里再举一个例子,求若干浮点数的和:
double Sum(int argc, ...)
{double sum = 0.;//创建可变参数列表的头部指针,将指针指向列表第一个元素double* ptr = (double*)(&argc + 1);//遍历可变参数列表,求和for (int i = 1; i <= argc; i++){sum += *ptr;//指针指向下一个参数ptr++;}return sum;
}
这代码貌似没问题,但传入的参数列表仅限于 double 类型,如果传入的参数是一个整型变量呢?由于内部只能通过指针访问,根本无法知晓外部传入的变量类型,而且编译器也不会对可变参数列表中的参数类型作检查。
所以,如果列表的参数类型不一致,第一个参数除了附带参数的数量信息外,还应附带每个参数的类型。解决办法可以参照 printf 的第一个参数。在此之前,先了解一个点,函数在传参时,汇编指令会对参数进行类型提升和 4 字节对齐。也就是说,char、short 的类型会被提升为 int ,而 float 类型直接提升为 double 。
修改后如下:
//format字符串只允许d或f,不区分大小写
double Sum(const char* format, ...)
{double sum = 0.;int count = strlen(format);//创建可变参数列表的头部指针,将指针指向列表第一个元素char* ptr = (char*)(&format) + sizeof(char*);for (int i = 0; i < count; i++){//遇到字符d或者D以整型处理if (format[i] == 'd' || format[i] == 'D'){sum += (double)*((int*)ptr);//指针指向下一个参数ptr += sizeof(int);}//遇到字符f或者F以双精度浮点型处理else if (format[i] == 'f' || format[i] == 'F'){sum += *((double*)ptr);//指针指向下一个参数ptr += sizeof(double);}}return sum;
}
至此已经很接近 printf 的参数调用方式了。
3.3、可变参数列表宏
调用 stdio.h 头文件便可以使用专用于处理可变参数列表的四个宏:
va_list:用于创建读取可变参数列表的指针;
typedef char* va_list;
__crt_va_start:将可变参数列表的指针指向列表第一个参数;
#define __crt_va_start_a(ap, v) ((void)(ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v)))
#define __crt_va_start(ap, x) __crt_va_start_a(ap, x)
__crt_va_arg:获取可变参数列表的指针当前指向的参数,并将指针指向下一个参数;
#define __crt_va_arg(ap, t) (*(t*)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
__crt_va_end:用于销毁可变参数列表的指针。
#define __crt_va_end(ap) ((void)(ap = (va_list)0))
此外对上述 _INTSIZEOF 和 _ADDRESSOF 也需要作了解:
#define _ADDRESSOF(v) (&(v))
#define _INTSIZEOF(n) ((sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1))
上述的 _INTSIZEOF 比较难以理解。它的运算结果是 4 字节对齐,这个公式有点巧妙,有兴趣可以自行理解。
接下来先将上面的代码用这几个宏改造一下:
double Sum(const char* format, ...)
{double sum = 0.0;va_list ptr;__crt_va_start(ptr, format);for (int i = 0; i < strlen(format); i++){if (format[i] == 'd' || format[i] == 'D'){sum += __crt_va_arg(ptr, int);}else if (format[i] == 'f' || format[i] == 'F'){sum += __crt_va_arg(ptr, double);}}__crt_va_end(ptr);return sum;
}
不过这几个宏不推荐使用,因为随着编译器的不同,很可能某些编译器并不支持这些宏,可移植性大大降低。这里主要是提供宏的思路,至于宏的实现也已经展示,各位完全可以根据这些宏通过纯 C 代码实现。
相关文章:
深入理解C语言的函数参数
1、一个简单的函数 int Add(int x, int y) {return x y; }int main() {printf("%d", Add(2, 3, 4, 5, 6));return 0; } 这一段足够简单的代码,闭眼都能知道运行结果会在屏幕上打印 5 。那编译器是怎么处理后面的 4、5、6 ? 我们再看看这个函…...
【C++】策略模式
目录 一、简介1. 含义2. 特点 二、实现1. 策略接口(Strategy Interface)2. 具体策略类(Concrete Strategies)3. 上下文类(Context)4. 使用策略模式 三、总结如果这篇文章对你有所帮助,渴望获得你…...
什么时候使用匿名类,匿名类解决了什么问题?为什么需要匿名类 ?
匿名类通常在以下场景下使用: 一次性使用: 当你需要创建一个类的实例,但该类只在一个地方使用,而不打算在其他地方重复使用时,可以考虑使用匿名类。 简化代码: 当创建一个小型的、一次性的类会让代码更简洁…...
怎么让gpt帮忙改文章 (1) 快码论文
大家好,今天来聊聊怎么让gpt帮忙改文章 (1),希望能给大家提供一点参考。 以下是针对论文重复率高的情况,提供一些修改建议和技巧: 怎么让GPT帮忙改文章 一、背景介绍 随着人工智能的发展,自然语言处理技术已经成为了许…...
Android源码下载流程
1.使用repo方式: https://github.com/jeanboydev/Android-ReadTheFuckingSourceCode/blob/master/article/android/framework/Android-Windows%E7%8E%AF%E5%A2%83%E4%B8%8B%E8%BD%BD%E6%BA%90%E7%A0%81.md 2.使用git方式: Windows 环境下载 Android 源…...
ArrayList与顺序表(带完整实例)
【本节目标】 1. 线性表 2. 顺序表 3. ArrayList的简介 4. ArrayList使用 5. ArrayList的扩容机制 6. 扑克牌 1.线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表…...
智能冶钢厂环境监控与设备控制系统(边缘物联网网关)
目录 1、项目背景 2、项目功能介绍 3、模块框架 3.1 架构框图 3.2 架构介绍 4、系统组成与工作原理 4.1 数据采集 4.2 指令控制 4.3 其他模块 4.3.1 网页、qt视频流 4.3.2 qt搜索进程 5、成果呈现 6、问题解决 7、项目总结 1、项目背景 这个项目的背景是钢铁行业的…...
【Python】conda镜像配置,.condarc文件详解,channel镜像
1. conda 环境 安装miniconda即可,Miniconda 安装包可以到 http://mirrors.aliyun.com/anaconda/miniconda/ 下载。 .condarc是conda 应用程序的配置文件,在用户家目录(windows:C:\users\username\),用于…...
实战章节:在Linux上部署各类软件
详细资料见文章的资源绑定 一、前言 1.1 为什么学习各类软件在Linux上的部署 在前面,我们学习了许多的Linux命令和高级技巧,这些知识点比较零散,同学们跟随着课程的内容进行练习虽然可以基础掌握这些命令和技巧的使用,但是并没…...
铭飞CMS list 接口 SQL注入漏洞复现
0x01 产品简介 铭飞CMS是一款基于java开发的一套轻量级开源内容管理系统,铭飞CMS简洁、安全、开源、免费,可运行在Linux、Windows、MacOSX、Solaris等各种平台上,专注为公司企业、个人站长快速建站提供解决方案 0x02 漏洞概述 铭飞CMS在5.2.10版本以前list 接口处存在sql注入…...
Linux指令初始
1.ls指令 语法 : ls [ 选项 ][ 目录或文件 ] 功能 :对于目录,该命令列出该目录下的所有子目录与文件。对于文件,将列出文件名以及其他信息。 ls 常用:-a 列出目录下的所有文件,包括以 . 开头的隐含文件。 …...
Nginx命令---启动nginx
介绍 使用命令启动nginx。 命令 nginx目录/bin/nginx...
【UE5】监控摄像头效果(下)
目录 效果 步骤 一、多摄像机视角切换 二、摄像头自动旋转巡视 三、摄像头跟踪拍摄 效果 步骤 一、多摄像机视角切换 1. 打开玩家控制器“MyPlayerController”,添加一个变量,命名为“BP_SecurityCameraArray”,类型为“BP_SecurityCa…...
binkw32.dll丢失怎么办?这5个方法都可以解决binkw32.dll丢失问题
binkw32.dll文件是什么? binkw32.dll是一个动态链接库文件,它是Windows操作系统中的一个重要组件。它包含了许多用于处理多媒体文件的函数和资源,如视频、音频等。当我们在电脑上打开或播放某些多媒体文件时,系统会调用binkw32.d…...
C语言-每日刷题练习
[蓝桥杯 2013 省 B] 翻硬币 题目背景 小明正在玩一个“翻硬币”的游戏。 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零),比如可能情形是 **oo***oooo,如果…...
Qt设置类似于qq登录页面(ikun)
头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QWindow> #include <QIcon> #include <QLabel> #include <QMovie> #include <QLineEdit> #include <QPushButton>QT_BEGIN_NAMESPACE namespace Ui { class…...
Qt 如何使用VTK显示点云
开发环境 ubuntu 20.04 VTK 8.2 编译VTK 下载源码 git clone --recursive https://gitlab.kitware.com/vtk/vtk.git 使用版本管理工具,切换版本到8.2 更改编译选项,这里使用cmake-gui进行配置 1、编译类型修改为Release 2、安装路径可以设置…...
Ganache结合内网穿透实现远程或不同局域网进行连接访问
文章目录 前言1. 安装Ganache2. 安装cpolar3. 创建公网地址4. 公网访问连接5. 固定公网地址 前言 Ganache 是DApp的测试网络,提供图形化界面,log日志等;智能合约部署时需要连接测试网络。 Ganache 是一个运行在本地测试的网络,通过结合cpol…...
Qt槽函数不响应不执行的一种原因:ui提升导致重名
背景: 一个包含了组件提升的ui,有个按钮的槽函数就是不响应,于是找原因。 分析: 槽函数的对应一是通过connect函数绑定信号,二是on_XXX_signal的命名方式。界面上部件的槽函数通常是第二种。 我反复确认细节&#…...
vuepress路径问题,导致图片不显示
图片不显示,报 Uncaught SyntaxError: Unexpected token <错误 很可能就是:路径配置原因 1.当设置为 / 时,VuePress 会假设你的站点将部署到服务器的根路径, 例如 https://yourdomain.com/。 2.生成的页面链接和资源引用将以…...
基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
Spring AI 入门:Java 开发者的生成式 AI 实践之路
一、Spring AI 简介 在人工智能技术快速迭代的今天,Spring AI 作为 Spring 生态系统的新生力量,正在成为 Java 开发者拥抱生成式 AI 的最佳选择。该框架通过模块化设计实现了与主流 AI 服务(如 OpenAI、Anthropic)的无缝对接&…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek
文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama(有网络的电脑)2.2.3 安装Ollama(无网络的电脑)2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...
基于SpringBoot在线拍卖系统的设计和实现
摘 要 随着社会的发展,社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统,主要的模块包括管理员;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...
