编写硬件驱动程序编写,里面调用的函数,归根结底是谁实现的?

基于超级结技术的功率MOSFET已成为高压开关转换器领域的……
激光雷达,是利用激光、全球定位系统GPS和惯性测量装置(……
每年,在新款iPhone发布之前各种各样的消息以及渲染图都……
Type&C自问世以来,经历过了风口浪尖上的2015年,……
前段时间有调查显示,中国使用手机时长全球第二!但是,长……
演讲人:陈智鸿时间: 10:00:00
演讲人:梁国柱,徐玮时间: 10:00:00
演讲人:张清刚时间: 10:00:00
预算:¥100,000-¥500,000预算:小于¥10,000
驱动程序如何实现设备函数对外围设备操作及控制解析方案
[导读]1 引言在Linux系统中,所有的外部设备都被看作是目录/dev下的一个文件,也就是系统把外部设备当作特殊文件来处理,并为外部设备提供一种标准接口,使得系统像访问文件一样访问外部设备。在嵌入式Linux中,同样也是把
在Linux系统中,所有的外部设备都被看作是目录/dev下的一个文件,也就是系统把外部设备当作特殊文件来处理,并为外部设备提供一种标准接口,使得系统像访问文件一样访问外部设备。在嵌入式Linux中,同样也是把外部设备当作文件来处理,应用程序通过调用标准的设备文件操作函数来打开、关闭、读取和控制设备,从事过Linux开发的人员都用到过上述设备控制函数,可它的实现机制很多开发人员并不清楚,所以开发过程中经常遇到一些难以解决的问题,为了便于理解整个实现过程,下面先分析设备驱动程序。
2 设备驱动程序
2.1驱动程序的功能
驱动程序设计是嵌入式Linux开发中十分重要的部分,驱动程序是应用程序与硬件之间的一个中间软件层,应该为应用程序展现硬件的所有功能,不应该强加其它的约束,对于硬件使用的权限和限制应该有应用程序层控制。要实现设备函数对外围设备的操作和控制,首先必须分析驱动程序的构成和实现原理。
2.2驱动程序的基本结构及实现
嵌入式Linux设备驱动程序都有一些共性,就是编写所有类型的驱动程序都通用的,操作系统提供给驱动程序的支持也大致相同。这些特性包括:
2.2.1两个重要的函数
(1)设备的注册和初始化mydriver_init()函数
static int mydriver_init(void){
i = register_chrdev(MYDRIVER_MAJOR,&mydriver&,& mydriver_fops);
i = register_chrdev(MYDRIVER_MAJOR,&mydriver&,& mydriver_fops); 这是一个驱动程序的精髓,当执行insmod命令时,这个函数实现3个功能:第一,申请主设备号;第二,在内核中注册设备的名字;第三,指定fops方法。其中所指定的fops方法就是用户对设备进行操作的方法,例如 read,write,open,release等.
(2) 驱动清除mydriver_cleanup()函数
static void mydriver_cleanup(void)
unregister_chrdev(MYDRIVER_MAJOR,&mydriver&);
该函数在执行rmmod的时候被调用,主要功能是卸载驱动程序.
2.2.2 file_operations 结构
每一个文件都有一个file的结构,在这个结构中有一个file_operations的结构体,这个结构体指明了能够对该设备文件进行的操作, 如何实现这些操作,是编写设备驱动程序大部分工作量所在。下面是本文所举示例的file_operations结构:
设备short_ch对应的fops方法是这样声明的:
struct file_operations short_fops = {
NULL, // short_lseek
short_read,
short_write,
NULL, // short_readdir
NULL, // short_poll
NULL, // short_ioctl
NULL, // short_mmap
short_open,
short_release,
NULL, // short_fsync
NULL, // short_fasync
其中NULL的项目就是不定义这个功能。可以看出short_ch设备只提供了read, write, open, release功能。其中write 功能在下面(3)中实现了,具体的实现函数起名为short_write。这些函数就是真正对设备进行操作的函数,不管实现的时候是多么的复杂,但对用户来看,就是这些常用的文件操作函数。
2.2.3文件操作函数的实现
为了便于阐述和分析,把核心空间中的一个长度为20的数组 tbuf[20]做为一个设备。通过用户程序对它实现open,read,write,close操作。这个设备的名字我称为short_ch。我们编写如下的函数,这个write函数可以向核心内存的一个数组里输入一个字符串。
int short_write (struct inode *inode, struct file *filp, const char *buf,
int count){
int retval =
extern unsigned char kbuf[20];
if(count&20)
copy_from_user(kbuf, buf, count);
3设备函数的实现过程分析
在嵌入式Linux下对设备操作的时候,一般都会用到read、 write、llseek和ioctl 等函数,通过这些函数可以像使用文件那样使用外部设备。这些函数的实现过程基本上是类似的,下面以write函数为例来分析用户使用write函数怎么把数据写到设备里面去。
3.1应用程序中函数的格式
用户程序中的write函数有三个参数,函数格式如下:
write(int fd, char *buf, int count)
其中参数fd表示将对之进行写操作的设备文件打开时返回的文件描述符.参数buf是一个指向缓冲区的指针,该指针指向存放将写入文件的数据的缓冲区.参数count表示本次操作所要写入文件的数据的字节数.fd一般大于3,0-2被系统分配给了默认的终端设备.
3.2驱动程序中函数的格式
上面驱动程序函数定义中我们看到驱动程序里的write函数有四个参数,函数格式如下:
short_write (struct inode *inode, struct file *filp, const char *buf, int count) inode 是设备节点指针,其中有设备号等信息,它能够告诉操作系统应该使用哪一个设备驱动程序,filp指针中有fops信息,可以告诉操作系统相应的fops方法函数在那里可以找到,后两项参数和应用程序中的含义相同。
3.3应用程序中函数和驱动程序中函数的参数传递
从上面可以知道两个函数参数个数不同,当应用程序的write函数执行时,是怎么调用驱动程序中相应的write函数的呢?其实关键是Linux系统内核中的相应函数 sys_write,这也是最不透明最不容易理解的地方. Linux 内核中sys_write的源代码:
asmlinkage ssize_t sys_write(unsigned int fd, const char * buf, size_t count)
struct file *
struct inode *
ssize_t (*write)(struct file *, const char *, size_t, loff_t *); // 指向驱动程序中的wirte函数的指针
lock_kernel();
ret = -EBADF;
file = fget(fd); // 通过文件描述符得到文件指针
if (!file)
if (!(file-&f_mode & FMODE_WRITE))
inode = file-&f_dentry-&d_ // 得到inode信息
ret = locks_verify_area(FLOCK_VERIFY_WRITE, inode, file, file-&f_pos,count);
ret = -EINVAL;
if (!file-&f_op || !(write = file-&f_op-&write)) // 将函数开始时声明的write函数指针指向fops方法中对应的write函数
down(&inode-&i_sem);
ret = write(file, buf, count, &file-&f_pos); // 使用驱动程序中的write函数将数据输入设备,注意看,这里就是四个参数了
up(&inode-&i_sem);
fput(file);
unlock_kernel();
从上面的函数功能可以看出, sys_write函数实现了应用程序中write向驱动程序中的short_write的参数传递过程,其中上述注释语句详细地阐述了参数由三个到四个的变化过程。
总的来说,设备函数的实现过程由下面几个步骤来完成:
(1) 加载驱动程序。驱动程序中的初始化函数申请设备名和主设备号,这些可以在/proc/devieces目录中查看到。 (2)从/proc /devices中获得主设备号,驱动程序加载成功后建立设备节点文件。通过主设备号将设备节点文件和设备驱动程序联系在一起。设备节点文件中的file 属性中指明了驱动程序中fops方法实现的函数指针。 (3)用户程序使用open打开设备节点文件,这时操作系统内核知道该驱动程序工作了,就调用 fops方法中的open函数进行相应的工作。 (4)当用户使用write函数操作设备文件时,操作系统调用内核中的sys_write函数,该函数首先通过文件描述符得到设备节点文件对应的inode指针和filp指针。 (5)然后sys_write才会调用驱动程序中的write方法来对设备进行写的操作。用户的write函数和驱动程序的write函数通过系统调用sys_write联系到了一起。本文以设备文件操作控制函数write为例来阐述整个函数的调用过程,其它函数的过程基本相同,本文不再详述。
本文的创新点在于阐述了嵌入式应用程序中对外部设备操作控制函数的实现机制及具体过程的分析,在目前的文献中很少有具体的分析,是作者在具体开发过程中的经验总结。
6年前,云计算创业“无人喝彩”,6年后的今天,阿里云已成长为全球领先的云计算服务平台。昨日,首届阿里云分享日暨云栖大会北京峰会召开,其中的医疗论坛引发国内互联网医疗创业企业和V C的关注。自去年启动“云合计划”后,阿里云上已汇聚5......关键字:
孟庆建市场普遍预计5G商用要等到2020年,但运营商已表现的迫不及待,欲提前摘取5G关键技术在空窗期商用。近日,华为、中兴双双与软银等运营商伙伴发布了“后4G”时代技术解决方案。7月13日,中兴通讯与日本软银正式签订5G战略合作协议,双方将......关键字:
两年前,Google推出Chromecast电视棒,由于可以插入电视机的HDMI接口让用户在电视上播放网络传输的视频内容,这款售价低廉(35美元)的产品迅速在亚马逊上卖到脱销。但在一片叫好声中,也有人称Chromecast存在一些问题,比如......关键字:
 一、 固网移动融合已成为行业主流趋势  FMC,直观的理解是固定与移动融合,但运营商在实施过程中,怎么进行融合,融合到什么程度则有不同的方式和路线。  固定和移动融合的方式体现在业务、技术、网络、体制、......关键字:
早在 3 月的时候我们就听到了优酷、土豆将合二为一的消息,但直到今天两者才在香港召开了双方股东大会。会上对合并方案进行了投票,结果是高票通过,等待了 5 个月的优酷土豆集团公司终于正式成立了。合并通过 100%......关键字:
消息,7月14日,华为携手中国移动、日本软银等众多产业合作伙伴共同发布了TDD+解决方案。据悉,TDD+是TDD技术的长期演进,是4.5G的核心组成部分。相信大家都会疑惑,为什么会有4.5G?华为无线网络产品线总裁汪涛表示,现有的网络技术无......关键字:
下班路上,打开手机点几下,家里的灯自动开了、空调开始制冷、热水器自动烧水;这些早已不是科幻电影中的场景,近两年越来越热的智能家居正将这些逐步变为现实。但需要明确的是,是逐步,不是已经,智能家居看似迎来了春天,步伐却还停留在冬天。据一组中关村......关键字:
剑桥咨询(Cambridge Consultants)的研究发展中心研制开发了一种健康概念产品&&&i-dration&,为热爱运动的人们提供最佳补水方案。上图中这个外形像拉长了的水滴状的瓶子......关键字:
现在很对国内的智能手机用户支付的时候都选择支付宝,不过现在又有了一个新选择,那就是中国银联与UC优视浏览器合作推出来的移动支付解决方案。这次方案覆盖的平台包括Symbian,iOS,Android和WindowsPhone,Java平台......关键字:
我 要 评 论
热门关键词君,已阅读到文档的结尾了呢~~
雷达干扰机中的数据采集及波形产生测试卡的研制机的,卡的,测试,雷达干扰机,测试雷达,测试卡,雷达干扰,波形垫圈,波形图,波形护栏
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
雷达干扰机中的数据采集及波形产生测试卡的研制
举报该文档为侵权文档。
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
将文档分享至:
分享完整地址
文档地址:
粘贴到BBS或博客
flash地址:
支持嵌入FLASH地址的网站使用
html代码:
&embed src='/DocinViewer-4.swf' width='100%' height='600' type=application/x-shockwave-flash ALLOWFULLSCREEN='true' ALLOWSCRIPTACCESS='always'&&/embed&
450px*300px480px*400px650px*490px
支持嵌入HTML代码的网站使用
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口linux驱动编写可以用库函数吗?【stm32吧】_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:25,892贴子:
linux驱动编写可以用库函数吗?收藏
rt,自学了段时间32,都是用库函数的,现在想学linux,找了几个学习视频都是用寄存器控制的。相当的不习惯。
国美网购商城,816全场触底价,品牌钜惠,万件商品,更有免单大奖,&惊&喜连连,!上国美购物,千万优惠券抢先领取,就是这么任性!
不行的,驱动开发环境都没有,通常都是文本编辑,交叉编译,加载验证,这行不是那么好混的
楼上有点不专业,谁说驱动就要用文本编辑了,编辑器想用哪个都可以。这个不是重点,重点是要搞清楚为什么写内核驱动不能用库函数!我来简单说一下吧,内核驱动又叫内核模块,是可以动态加载到内核中成为内核的一部分的代码。内核的作用就是为上层应用提供软件环境。库函数大多都是通过系统调用实现的,系统调用就是从内核导出的一些函数,库函数是应用层的函数,驱动是在内核层,在内核没启动的时候就系统调用是不会成功的,你在内核里面掉库函数就相当于内核跑到应用层去调应用层函数,而这个应用层函数又回到内核里面调内核函数,这个时候内核都没起来……啰嗦这么多,希望你们明白
STM32官方提供的库函数,本质就是操作寄存器啊,你不可以随便定位到一个库函数看看,基本都是前一句判断输入的数据是否满足要求,满足马上就配置寄存器……,所以用库函数开发驱动,我认为没问题啊……当然直接操作寄存器,会省掉判断数据这一步,理论上运行会更快,其实平时写的什么LCD驱动函数,触控驱动函数,等,本质上就是驱动程序啊
难道你们以为像单片机那样有个软件给你,写点代码,编译一下,驱动就出来了
登录百度帐号推荐应用0x09-未曾领略的新风景
前方曾提到两个关键字 restrict 和 inline 在C语言中的使用,但是后者可能还能带来些许理解上的便利,开启 -O3 优化是一个很不错的选择。
inline 的作用还是在于和 static 一起使用,让小函数尽可能的减小开销甚至消除函数开销。
restrict 最重要的还是在于编译器的优化上。编译器能够为我们的程序提供优化,这是众所周知的,但是编译器是如何优化的,知道的人少之又少,其中有一些优化是建立在编译器能够理解你的代码,或者说编译器要认为你的代码是可以被优化的情况下,才会采取优化措施:
有一个很重要的地方,称为指针别名,是阻碍编译器优化代码的最重要的地方
什么是指针别名?
void tmp_plus(int * a, int * b)
for(int i = 0; i & b_++i)
*a += b[i];
这段代码中,a, b 是两个被传入的指针,编译器对他们毫无所知,也不知道a是否在b的范围之内,故无法对其做出最大程度上的优化,这会导致什么结果呢?也就是,每依次循环过后,*a的结果都会写回到主存当中去,而不是在寄存器里迅速进行下一次增加!
或者有的聪明的编译器可以将其扩展成if ... else的加长版形式来避免写回操作。
但是如果我们增加了restrict
void tmp_plus(int * restrict a, int * restrict b) ...
这就是告诉编译器,这两个指针是完全不相干的,你可以放心的优化,不会出错。
但是在这里有一些小的问题,那就是C++并不支持这个关键字,这会导致什么后果?
你在Visual Studio下编程的时候会发现使用restrict关键字是会产生编译错误的,无论你使用 .c 还是 .cpp,难道说不支持吗?实际上不是,主流的编译器都对这个关键字有自己的实现
Visual Studio(Visual C++) : __restrict
GCC, Clang : __restrict__
剩下一个是前面也大概说过的 volatile,当时对其的解释就是让编译器不对其进行优化的意思,这里再说清楚一点
假设 volatile int i = 0;
首先它的现象本质就是,确保每次读取 i 的时候,是从它的内存位置读取,每次对它操作完毕后,将结果写回它的内存位置,而不是将其优化保存在寄存器内。
这就让一些编译器的优化无法进行,就像上方所说的。
一般将其用在调试时期,防止编译器的优化对自己的代码逻辑造成混淆。
但是,正如上面所说,这个关键字的作用是每次都进行存取,开销自然就变大了,意味着无法使用缓存来对其进行加速,换句话来说就是,只要是关于它的操作,开销都将变大。
并且,其所能起到的作用大部分体现在 多线程编程中,而且也无法阻止指令重排之类的优化。
对此,有一个需要提及的内容是,可以适当的使用 内存屏障 来替代这种volatile的功能,内存屏障是由操作系统提供的功能,目的是防止由于某些优化,导致的指令重排的效果。
某些编译器也有提供类似的功能,例如 GCC就可以通过内嵌汇编代码的方式实现这个效果
以上的略微提及,详细可以自行查阅资料。
在常见C中,数组是这样的。
int arr_1[3];
int arr_2[] = {1, 2, 3}; /* 创建三个元素的数组 */
C99之后,可以使用一种叫做 复合文字(Compound Literal)的机制来做到更多的事情,最简单的就是创建匿名数组(看着有点像C++11引进的 Lambda匿名函数):
int *ptoarr = (int[]){1, 2, 4}; /* 之后可以使用 ptoarr 操作 */
ptoarr[2] = 0;
printf("The Third number is : %d", ptoarr[2]);
输出: $ The Third number is : 0
当然,这种机制并不是只能如此使用,稍微高级一点的应用是,可以传递数组了,无论是按参数传递还是返回值。
int *test_fun(int most[], int length){
for(int i = 0;i &++i)
return (int []){most[0], most[1], most[2], most[3]...};/* so on */
test_fun((int []){6,6,6,6,6}, 5);
这也是自从更新了C99标准以后,可以讲某个整体进行返回的例子,也包括结构体:
typedef struct compond{
int arrays[10];
//假设有test_fun函数返回该结构体
return (combond){
1, // 给value
2, // 给number
{most[0], most[1], most[2], most[3]...}}; //给arrats
当然也可以构造完成之后再返回实体,不过这么做不如上面写的效果好,原因前方已经提过。
稍微修改一下结构体,又是另一番情况:
typedef struct compond{
int arrays[]; /* 这里不再显式声明大小,也就无法构造实体 */
这个方式很像前方提到的 前桥和弥的 越界结构体 的例子,只不过这个是一个在C标准允许的情况下,而前桥和弥则是利用一些C语言标准的漏洞达到目的。
在使用这种结构体的时候,首先要为其动态分配好空间,之后通过指针进行操作,也增建了内存泄漏的风险,所以仁者见仁智者见智了:
compond* ptocom = malloc(sizeof(compond) + num_you_want * sizeof(int));
/* 这样就成功分配了足够的空间 */
ptocom-&arrays[0] = some_
free(ptocom);
ptocom = NULL;
这其实并不是这种机制的目的,我觉得这种复合文字机制的最大用处还是在于消除艰涩难懂的函数调用
例如有一个函数的参数列表及其之长,我们就应该考虑使用新机制结合结构体,来对这个函数重新修饰一番:
int bad_function(double price, double count, int number,
int sales, Date sale_day, Date in_day,
String name, String ISBN, String market_name,
); /* 实现省略 */
这种函数,在陌生的他人拿到之后,一定头疼不已,可以对它进行一些处理,来减轻使用时候的苦恼:
/* 首先使用宏进行包裹 */
#define good_function(...) {\
/* 使用这个宏作为接口,可传入不限个数的参数 */
接下来定义一个结构体,用于参数的接收。
/* 接收参数的结构体 */
typedef struct param{
/* 销售价格 */
/* 折扣 */
/*总数量*/
/*销售数量*/
/* 销售日期 */
/* 进货日期 */
/* 货物名称 */
String ISBN;
/* ISBN号 */
String market_
/* 销售市场 */
/* 并配上文档说明每个参数的作用 */
其次继续完成宏
/* 此时将函数的声明改为: */
int bad_function(param input);
#define good_function(...) {\
bad_function((param){__VA_ARGS__});\
这就完成了包裹
使用的时候:
good_function(.price = 199.9, .count = 0.9,
.number = 999, .sale = 20 /*and so on*/)
也可以在宏利使用默认参数,以此来减少一些不必要的工作量,达到像其他高级语言一样的函数默认参数的功能。当然如果不添加默认的值,则会按照标准将其值初始化为 0 或者 NULL.
#define good_function(...) {\
bad_function((param{.price = 100.0, .count = 1.0, __VA_ARGS__})); \
/* 假设想要设置默认价格为100, 默认折扣为 1.0 */\
较之C89(C90)的提取可变宏参数要来的更加灵活及&高效&。
至于 __VA_ARGS__ 宏的较为官方的用法,前人之述备矣,就不在这里记录了。
C11之 _Generic
只看名字就能明白这是C语言支持泛型的兆头。
好像很有意思
不过某些地方依旧有些限制,比如对于选择函数方面。
/* -std=c11 */
void print_int(int x) {printf("%d\n", x);}
void print_double(double x) {printf("%f\n", x);}
void print(){printf("Or else, Will get here\n");}
#define CHOOSE(x) _Generic((x),\
int : print_int,\
double : print_double,\
default : print)(x)
int main(void)
CHOOSE(11.0);
/* 11.000000 */
CHOOSE(11.0f); /* Or else, Will get here */
缺点就在于,: 后面无法真正的调用函数,而是只能写上函数名或者函数指针, 当然为了突破这一点可以使用宏嵌套来间接实现这一点,但是归根结底,无法在 : 后面调用函数。
#define CHOOSE(X) _Generic((x), \
int : prinf("It is Int")\
double : printf("It is double"))(x)
/* Compile Error! */
这样做会导致编译错误,编译器会告诉你 CHOOSE并不是一个函数或者函数指针,看起来错误很无厘头,实际上一想,你要是在 : 之后调用了函数,那么左后一个括号该如何自处,唯一的办法就是返回函数指针:
typedef void (*void_p_double)(double);
typedef void (*void_p_int)(int);
void print_detail_double(double tmp){
printf("The Double is %f\n", tmp);
void print_detail_int(int tmp){
printf("The Int is %d\n", tmp);
void_p_int print_int(){
printf("It is a Int! ");
return print_detail_
void_p_double print_double() {
printf("It is a Double! ");
return print_detail_
void print_default(){printf("Nothing Matching !\n");}
#define CHOOSE(x) _Generic((x),\
int : print_int(x),\
double : print_double(x),\
default : print_default)(x)
CHOOSE(11);
/* It is a Int The Int is 11 */
CHOOSE(11.0);
/* It is a Double The Double is 11.000000 */
CHOOSE(11.Of); /* Nothing Matching ! */
choose(11l);
/* Nothing Matching ! */
对于宏而言,最新的编译器支持, #program once, 将这个放在头文件中,就代表该头文件只编译一次,也就是说,可以替代原有的老式 #ifdef的三段式保护,具体编译器支持请查询各编译器。
函数返回实体
许多年前,在C编程的普遍常识是,返回指针,而不是一个实体。
但是现在,在这个C99(C11)世纪,早已经打破这个局限,无论是从程序员编写的语法角度看,亦或者是从编译器的优化角度看,都不在需要特地的将一个实体表示为指针进行返回。
combine* ret_struct(combine* other){
/* 这里的参数也是指针,因为当时并不允许直接给结构体进行赋值 */
int value = other-&filed_
/* SomeThing to do */
combine* p_local_ret_com = malloc(sizeof(combine));
/* 一系列安全检查 */
return p_local_ret_
这在当下自然也是可以的,而且会有不错的性能,但是。但是这也是C语言最令人诟病的地方,你却深深的踏了进去。
尽量少用 malloc(calloc, realloc) 之类的内存操作函数,是现代C编程的一个指标,在这个函数中,我们没有办法保证分配出去的内存能够回收(因为就这个函数而言并没有回收这个内存),虽然现代计算机(非特殊机器)的内存已经不在乎那几十个甚至几百个中等结构体的内存泄漏,但是内存泄露依然是C语言最严重的问题,没有之一。
我们该做的就是尽量减少风险的发生率:
combine ret_struct(combine other){
/* C99之后,我们就开始允许直接给结构体赋值,
意味着可以直接返回结构体了 */
combine loc_ret_ /* 如果没有复合的结构体成员的话,各成员会自动初始化为0,不必担心初始化问题 */·
/* Do SomeThing to 'loc_ret_com' with 'other' */
return loc_ret_
/* main */
int main(void)
combine preview = {...};
combine action = ret_struct(preview);
这么做的目的自然是为了让我们的风险降到最低,让系统栈帮我们管理内存,包括创建-&使用-&回收,这个过程(就像被其他语言所津津乐道的GC机制,实际上C语言程序员可以选择自己实现一个垃圾回收机制,在本系列的最后面可能会做一个简易的回收机制供大家参考,但是首先让我们看完风景,再用一个实际程序串联起来后,再去考虑GC)不需要你来操心。
但是这真的是最好的形式了吗?
让我们回想一下C语言在调用函数的时候发生的某些事情,因为最开始的我们是从 main 函数的调用开始我们的程序.
也就是说,系统在栈上位这个函数分配了空间
紧接着我们调用了函数 ret_struct
调用之后,为了保存现有状态,栈里会被压入许多信息,包括当下main的位置以及ret_struct的各种参数等等,其中有一个东西就是返回地址
这个被压入的元素保证了在执行完ret_struct之后我们能够顺利的返回main调用它的位置继续执行
这个和我们要讲的有什么关系呢?
没关系我会乱说 = =
一般来说,在函数返回一个值(把所有对象,值都称为值)时,由于这个值是在函数中创建的(无论是传入的参数,还是在函数里创建的非static对象,即便是static或者全局变量情况也是一样只是不符合这个假设结论罢了),所以在函数结束后,栈空间被回收,它就被默认的销毁了(可以参考前桥和弥的书里有这个的解释,实际上值并没有真正被销毁了,但是不允许再用,否则视为非法),但是我们是怎么接收到函数的返回值的?
当然是因为程序帮你拷贝了一份这个值的副本的原因啊。
而这个副本再使用过以后就会立即被销毁,那么我们如果像上方那么返回一个结构体的话会发生什么应该就很清晰了:复制副本-&销毁本地的原身-&将这个副本的值赋给外部接收的变量(没有则销毁)-&销毁副本
这有什么问题,难道还有更好的方法?
那自然有啊
现代科技飞速发展,编译器也不甘示弱,只要你外部有接收的地址,在(不开优化的情况下,开了优化也可能因为版本问题或者某些不可抗力而不优化)直接return对象的情况下,是可以省去副本的操作的
也就是说:
/*改写上方代码*/
combine ret_struct(combine other){
other-&filed_value = ...;
/* SomeThing to other */
return (combine){ .filed_value = other-&filed_value
如果这么写,编译器就知道,哟!你是想要把这个对象放到外边使用是吧,那我懂了,就直接找到外边接收这个值得变量地址,不再创建副本(其实还是创建,只不过不再销毁而已),而是在那个变量地址中写入这个对象。
这就实现了让系统帮你管理内存的目的,而不是担心是否没有释放内存带来的风险,而且还优化了性能,何乐而不为。
注:关于上方提到的 开了优化也可能因为版本问题或者某些不可抗力而不优化 这个说法是有道理的,因为大家的编译器版本都不一样,有的人用老版本那自然没有这个优化了,有的则是因为你编写的程序逻辑上的构造导致编译器无法为此处产生如此的优化,这个请参考前方提到的书本的优化章节。让然编译原理要是能看自然更清楚喽(ps:我还没看)
题外话:这个方法对于C++同样适用

我要回帖

更多关于 如何编写驱动程序 的文章

 

随机推荐