嵌入式C语言自我修养

所需积分/C币:50 2019-01-26 08:50:55 9.41MB PDF
收藏 收藏 2
举报

Linux内核中的GNU C语法扩展精讲,嵌入式C语言进阶教程,C语言嵌入式Linux高级编程
什么是C语言标准呢?我们生活的现实世界,就是由各种标准构成的,正是这些标准,我们的社会才会有条不紊的运行。比如我们过马 路,遵循的交通规则就是一个标准:红灯停,绿灯行,黄灯亮了等一等。当行人和司机都遵循这个默认的标准时,我们的交通系统才会顺 畅运行。电脑中的USB接口也是一种标准,当大家生产的USB产品都遵循USB协议这种通信标准时,我们的手机、U盘、USB像 头、US日网卡才可以在各种电脑设备上互插互拔。2G、3G、4G也是一种标准,当不同厂家生产的基带芯片都遵循这种通信标准,我们 所用的不同品牌、不同操作系统的手机才可能互相打电话、互相发微信、互相给对方点。 同样,C语言也有它自己的标准。我们知道,C语言程序需要通过编译器,编译生成二进制指令,才能在我们的电脑上运行。在C语言刚 发布的早期,各大编译器厂商开发自己的编译器时,各自开发,各自维护,时间久了,就会变得比较混乱。这就会造成这样一种局面:程 序员写的程序,在一个编译器上编译通过,在另一个编译器编译通不过。大家按各自的习惯来,谁也不服谁,就像春秋战国时代:不同的 货币、不同的度量衡,不同的文字,都是中国人,因为标准不统一,所以交流起来很麻烦,这样下去也不是办法啊。 后来ANS( AMERICAN NATIONAL STANDARDS INSTITUTE:美国国家标准协会,简称ANS|)出山了,联合O(国际化标准组织)召 集各个编译器厂商大佬,各种技术团体,一起喝个茶、开个碰头会,开始启动C语言的标准化工作。期间各种大佬之问也是矛盾重重,充 满各种争议,但功夫不负有心人,经过艰难的磋商,终于在1989年达成一致,发布了C语言标准,后来第二年又做了一些改进。于是, 就像秦始皇统一六国、统一文字和度量衡一样,C语言标准终于问世了!因为是在1989年发布的,所以人们一般称其为C89或C90标 准,或者叫做ANS|C。 1.3C标准内容 C标准里主要讲了什么? C标准英文文档,洋洋洒洒几百页,讲了很多东西,但总体归纳起来,主要就是C语言编程的一些语法惯例,比如 定义各种关键字、数据类型 定义各种运算规则 各种运算符的优先级和结合性 数据类型转换 变量的作用域 函数原型 ·函数嵌套层数 ·函数参数个数限制 ·标准库函数 C标准发布后,大家都遵守这个标准∶程序员开发程序时,按照这种标准写;编译器厂商开发编译器时,也按照这种标准去解析、翻译程 序。不同的编译器厂商攴持统一的标准,这样大家写的程序,使用不同的编译器,都可以正确编译、运行,大大提高程序的开发效率,推 动了行业的发展 14C标准的发展过程 C标准并不是永远不变的,就跟移动通信一样,也是从2G、36、4G到5G不断发展变化的。C标准也经历了下面四个阶段 K&R c ·ANS|C C11 K&r c K&RC一般也称为传统C。在C标准没有统一之前,C语言的作者 Dennis ritchie和 Brianκ kernighan合作写了一本书《C程序设计语 言》。早期程序员编程,这本书可以说是绝对权威。这本书很薄,内容精炼,主要介绍了C语言的基本使用方法。后来《C程序设计语 言》第二版闩世,做了一些修改∶比如新增 unsIgη ed int、 long int、 struct等数据类型;把运算符-+/-修改为+=/=,避免运算符带来 的一些歧义和巳ug。这本书可以看作是ANS标准的雏形。但早期的C语言还是很简单的,比如还没有定义标准库函数、没有预处理命令 ANSI C ANSI C是ANSI(美国国家标准协会)在K&RC的基础上,统一了各大编译器厂商的不同标准,并对C语言语法和特性做了一些扩展 而发布的一个标准。这个标准一般也叫做C89/C90,也是日前各种编译器默认支持的C语言标准。 ANSIC主要新增了以下特性 增加 signed、 volatile、 const关键字 ·增加void*数据类型 ·增加预处理器命令 增加宽字符、宽字符串 定义了C标准库 c99标准 C99标准是ANS1999年在89标准的础上新发布的一个标准,该标准对ANSC标准做了一些扩充,比如新增一些关键字,支持新的 数据类型 布尔型:Bool 复数: Complex 虚数: Imaginary 内联: inline 扫针修饰符: restrict ·支持 Flong long、 long double数据类型 支持变长数组 允许对结构体特定成员赋值 支持16进制浮点数、foat_ Complex等数据类型 除此之外,C99标准也借鉴其它语言的一些优点,对语法和函数做了一系列改进,大大方便了程序员开发程序,比如 变量声明可以放代码块的任何地方。ANSC规定变量的声明要全部写在函数语句的最前面,否则就会报编译错误。现在不需要这样 写了,哪里需要使用变量,在哪里直接声明使用即可 源程序每行最大支持4095个字节。这个貌似足够用了,没有什么程序能复杂到一行程序有4KB个字符 支持∥单行注释。ANS使用/*段没有++的注释方便,所以C99新标准借鉴过来了,也开始支持这种注释方式 ·标准库新增了一些头文件:如 stool.h、 complex. h、 stdarg. h、 feny. h等。大家在C语言中经常返回的true、 false,其实这也是 C+里面定义的bo类型。那为什么我们经常这样写,而编器编译程序时没有报错呢,这是因为早期大家编程使用的都是ⅤC+6.0 系列,是C++编译器。还有一种可能就是有些DE对这个数据类型的数据做了封装。 c11新标准 C11标准是2011年发布的最新C语言标准,修改了C语言标准的一些Bug、新增了一些特性 增加 Noreturn,声明函数无返回值; 增加_ Generic:支持泛型编程 修改了标准库函数的一些Bug:如gets()涵函数被gets_S(函数代替 新增文件锁功能; 支持多线程 从C11标准的修改内容来看,也慢慢察觉到匚语言末来的发展趋势:C语言现在也在借鉴现在编程语言的优点,不断添加到自己的标准里 面。比如现代编程语言的多线程、字符雫、泛型编程等,C语言最新的标准都攴持。但是这样下去,C语言是不是还能保持她简单就是 美"的优雅特色呢,我们只能慢慢期待了。但至少目前我们不用担心这些,因为C11新发布的标准,目前绝大多数编译器还不支持,所以 我们暂时还用不到。 15编译器对C标准的支持 标准是一回事,各种编译器攴不攴持是另一回事,这一点,大家要搞淸矬。这就跟手机一样,不同时期发布的手杋对通信标准支持也不- 样。早期的手机可能只支持2G通信,后来支持3G,现在发布的新款手机基本上都支持4G了,而且可以秉容2G/3G。 现在5G标准正在硏发,快发布了,据说2019年发布,2020年商用。但是目前还没有手机支持5G通信,就跟现在没有编译器支持C11 标准一样。 不同编译器,甚至对C标准的攴持也不一样。有的编译器只攴持ANSC,这是目前默认的C标准。有的编译器可以支持C99,或者攴持 C99标准的部分特性。目前对匚99标准支持最好的是GNUC编译器,据说可以支持匚99杬准9%的新増特性 1.6编译器对C标准的扩展 不同编译器,岀于开发环境、硬件平台、性能优化的需要,除了支持C标准外,还会自己做一些扩展 在51单片机上用C语言开发程序,我们经常使用 Keil for c51集成开发环境。你会发现 Keil for c51或其他|DE里的C编译器会对C语 言标准作很多扩展。比如增加各种关键字: ·data:RAM的低128B空间,单周期直接寻址 ·code:表示程序存储区; ·bit:位变量,常用来定义单片机的P0~P3管脚 ·sbit:特殊功能位变量; sfr:特殊功能寄存器 · reentrant:重入函数声明 如果你在程序中使用以上这些关键字,那么你的程序就只能使用51编译器来编译运行,你使用其它的编译器,比如艹6.0,是编译通不 同样的道理,GCC编译器,也对C标准做了很多扩展 零长度数组 语句表达式 内建函数 attribute特殊属性声明 标号元素 case范围 比如支持零长度数组。这些新增的特性,C标准目前是不支持的,其它编译器也不支持。如果你在程序中定义一个零长度数组 int alo 只能使用GCC编译器才能正确编译,使用ν艹+6.编译器编译可能就通不过,因为微软的α++编译器不攴持这个特性。 1.7本教程主要内容 在GNU开源软件、 Linux内核中会大量使用GCC自己扩展的语法,这会对我们理解开源软件、 Linux内核代码带来-定暲碍和扰。本 教程主要介绍 GNU O对C标准扩展的一些常用语法和使用。终极目标是看懂 Linux内核驱动、GNU开源软件中这些特殊语法的应用,扫 除这些特殊语法对我们理解内核代码带来的困扰和障碍。 1.8本教程需要的学习环境 在本教程讲解中,会使用一些arm-inux- gnuea bi-gc等命令用来编译和反汇编程序。所以在学习本教程之前,确保你的电脑上有如下 Linux环境或源代码: · Linux学习环境: Ubuntu、 Fedora等皆可; · arm-linux-gnueahi-gc交叉编译工具 · Linux内核源码: Linux44.x ·U-boo2016.09源代码 备注 如果怒手头暂时没冇 Linux学习坏境,也可以在 Windows坏境下安装-ree学习。教程中的语言示例程序在α-re坏境下面也能编 译通过。当然在这里,还是建议您使用虛拟机安装一个Linuⅹ学习环境,一个良好的环境更有利于我们的学习,在安装过程有什么疑惑, 可以通过邮件(3284757626@ gecom)与我联系,也可以加入QQ群(475504428),参与技术讨论。 第02课: Linux内核驱动中的指定初始化 21什么是指定初始化 在标准C中,当我们定义并初始化一个数组时,常用方法如下 inta[10]={0,1,2,3,4,5,6,7,8}; 按照这种固定的顺序,我们可以依次给a[o]和a8]赋值。因为没有对a明赋值,所以编译器会将a9默认设詈为0。当数组长度比较小 时,使用这种方式初始化比较方便。当数组比较大,而且数组里的非零元素并不连续时,这时候再按照固定顺序初始化就比较麻烦了。 比如,我们定义一个数组b[100],其中b10]、b30]霑要初始化,如果还按照前面的固定顺序初始化,仆中的初始化数据中间可能要填 充大量的0,比较麻烦 那忑么办呢?c99标准改进了数组的初始化方式,支持指定任意元素初始化,不冉按照固定的顺序初始化 intb[100]={[10]=1,[30]=2}; 通过数组索引,我们可以直接给指定的数组元素赋值。除此之外,一个结构体变量的初始化,也可以通过指定某个结构体域直接赋值。 因为GNUC支持C99标准,所以G∝C编译器也攴持这一特性。甚至早期不支持¢99,只攴持匚89的σα编译器版本,这一特性也被当 作一个GCC编译器的扩展特性来提供给程序员使用。 22指定初始化数组元素 在GNUC中,通过数组元素索引,我们就可以给某个指定的元素直接赋值 intb[100]={[10]=1,[30]=2} 在{}中,我们通过[10]数组元素索引,就可以直接给a[10]赋值了。这里有个细节注意一下,就是各个赋值之间用逗号"“"隔开,而不是 使用分号";" 如果我们想给数组中某一个索引范围的数组元素初始化,可以采用下面的方式 int main (void) intb[100]={[ 30]=1,[50 for (int i =0: i 100; i++) rinf("‰d if(i%10=0) printf("、n") return 0 在这个程序中,我们使用[10…30]表示一个索引范围,相当于给a[10]到a[30]之间的20个数组元素赋值为1。 GNUC支持使用….表示范围扩展,这个特性不仅可以使用在数组初始化中,也可以使用在 switch-case语句中。比如下面的程序 #includecstdio. h> int main (void) Int switch(i) case 1 printf( l\n") break rinf( %d\n",1) break; case 9 printf(9\n"); default printf( default!\n"); return o 在这个程序中,当case值为2到8时,都执行相同的case分支,可以通过case2..8:的形式来简化代码。这里同样也有一个细节需要注 意,就是…和其两端的数据范围2和8之间乜要空格,不能写成2…8的形式,否则编译就会通不过 23指定初始化结构体成员变量 跟数组类似,在标准C中,结构体变量的初始化也要按照固定的顺序。在GNUC中我们也可以通过结构域来初始化指定某个成员 struct student char name [20] Int age int main (void struct student stul= wit, 20 3 printf( %s: %d\n",stul name, stul age) struct student stu2 name ="wang li tao", rinf( %s: %d\n", stu2. name, stu2 age) return o 在程序中,我们定义一个结构体类型 student,然后分别定义两个结构体变量stu1和stu2。初始化stu1时,我们采用标准C的初始化方 式,即按照固定顺序直接初始化。初始化stu2时,我们采用GNUC的初始化方式,通过结构域名name和,age,我们就可以给结构体 变量的某一个指定成员直接赋值。非常方便。 24 Linux内核驱动注册 在 Linux内核驱动中,大量使用GNUC的这种指定初始化方式,通过结构体成员来初始化结构体变量。比如在字符驱动程序中,我们经 常见到这样的初始化 static const struct file_operations ab3100-_otp_operations =i open ab3100-_otp_open seq_read seg_ lseek release single_release 在驱动程序中,我们经常使用 file_operations这个结构体变量来注册我们开发的驱动,然后以回调的方式来执行我们驱动实现的相 关功能。结构体fi1e_ operations在 Linux内核中的定乂如下 struct file_operations t struct module *owner loff t (*llseek) (struct file * loff t, int) ssizet (*read)(struct file x, charuser * size t, loff t ) ssizet (write)(struct file *, const char __user *, size t, lofft *)i ssizet (read_iter) (struct kiocb * struct iov_iter * ssize t (write iter (struct kiocb *, struct iov iter * int C*iterate) (struct file *, struct dir_context *) unsigned int (* po11)(struct file, struct poll_table_struct *) long (*unlocked_ioctl) (struct file k, unsigned int, unsigned long) long (*compat_ ioctl) (struct file * unsigned int, unsigned long) int mmap)(struct file * struct vm_area_struct " int (*open) (struct inode * struct file *); nt ( flush)(struct file * fl_owner_t id) int (release) (struct inode *, struct file * int fsync)(struct file * loff_t, lofft, int datasync) int (*aio_fsync)(struct kiocb * int datasync); int (*fasync) (int, struct file *, int) int (*lock) (struct file *, int, struct file lock * ssize_t (*sendpage)(struct file *, struct page * int, size_t, loff_t", int); unsi gned long (get_unmapped_area)(struct file ", unsigned long, unsigned long, unsi gned long, unsigned long); int (check_flags)(int); int (*flock)(struct file * int, struct file_lock * ssize t (*splice write)(struct pipe inode info w struct file x, lofft *, size_t, unsigned int) ssize_t ("splice_read)(struct file * loff_t struct pipe_inode_info * size_t, unsigned int) nt (*setlease)(struct file * long, struct file lock ** void **) long (allocate)(struct file * file, int mode, loff_t offset, loff t len); void (*show_fdinfo)(struct seq_file*m, struct file *f); #i indef CoNFIg mmu unsigned (mmap_capabilities)(struct file * fendi 结构体file_ operations里面定义了很多结构体成员,而在这个驱动中,我们只初始化了部分成员变量,通过访问结构体的成员来指 定初始化,非常方便。 25指定初始化的好处 这种指定初始化方式,不仅使用灵活,而且还有一个好处就是:代码易于维护。尤其是在 Linux内核这种大型项目中,几万个文件,几干 万的代码量,当成百上千个文件都使用fie_ perations这个结构体类型来定y变量并初始化时,那么一个很大的问题就来了:如果 采用标准C那种按照固定顺序赋值,当我们的fie_ operations结构休类型发生改变时,如添加成员、减少成员、调鍪成员顺序,那 么使用该结构体类型定义变量的大量C文件都需要重新调整初始化顺序,牵一发而动全身,想想这是多么可怕! 我们通过指定初始化方式,就可以避免这个问题。无论file_ operaτions结构体类型如何变化,添加成员也好、减少成员也好、调整成 员顺序也好,都不会影响其它文件的使用。有了指定初始化,再也不用加班修改代码了:妈妈再也不用担心我们整日加班,不回家吃饭 多好! 第03课:宏构造利器:语句表达式 31基础复习:表达式、语句和代码块 表达式 表达式和语句是C语言中的基础慨念。什么是表达式呢?表达式就是由一系列操作符和操作数构成的式子。操作符可以是C语言标准规定 的各种算术运算符、逻辑运算符、赋偵运算符、比较运算符等。操作数可以是一个常量,也可以是一个变量。表达式也可以没有操作符 单独的一个常量甚至是一个字符串,也是一个表达式。下面的字符序列都是表达式 + ·=1++ wit 表达式一股用来数据计算或实现某种功能的算法。表达式有2个基本属性:值和类型。如上面的表达式2+3,它的值为5。根据操作符的不 同,表达式可以分为多种类型,如 关系表达式 逻辑表达式 条件表达式 赋值表达式 算术表达式 语句 语句是构成程序的基本单元,一般形式如下 表达式 表达式的后面加一个;就构成了一条基本的语句。编译器在编译程序、解析程序时,不是根据物珵行,而是恨据分号;来判断一条语句的结 束标记的。如ⅰ=2+3;这条语句,你写成下面的形式也是可以编译通过的 代码块 不同的语句,使用大括号括起来,就构成了一个代码块。C语言允许在代码块里定义一个变量,这个变量的作用域也仅限于这个代码块 内,因为编译器就是根掴↓来做入栈出栈操作来管理变量的作用域的。如下面的程序 int main(voi d) int i =3: printf("i=%d\n", i) int i =4. printf( i=%d\n",1) printf( "i-%d\n",1) turn o 运行结果为 i=3 32语句表达式 语句表达式的定义 GNUC对C标准作了扩展,允许在一个表达式里内嵌语句,允许在表达式内部使用局部变量、for循环和goo跳转语句。这样的表达 式,我们称之为语句表达式。语句表达式的格式如下 ({表达式1;表达式2;表达式3;}) 语句表达式最外面使用小括号(括起来,里面一对大括号仍}包起来的是代码块,代码块里允许内嵌各种语句。语句的格式可以是“表达 式;"这种一般格式的语句,也可以是循环、眺转等语句。 跟一殷表达式一样,语句表达式也有自己的值。语句表达式的值为内嵌语句中最后一个表达式的值。我们举个例子,使用语句表达式求 值 int main (void) Int sum =0; sum int s =0. for( int i =0; i< 10; i++) s=s+1 printf" sum = %d\n", sum) return 0 在上面的程序中,通过语句表达式实现了从1到10的累加求和,因为语句表达式的值等于最后一个表达式的值,所以在for循环的后面 我们要添加一个s;语句表示整个语句表达式的值。如果不加这一句,你会发现sum=0。或者你将这一行语句改为100;你会发现最后sum 的值就变成了100,这是因为语句表达式的值总等于最后一个表达式的值。 语句表达式内使用goto跳转 在上面的程序中,我们在语句表达式内定义了局部变量,使用了for循环语句。在语句表达式内,我们同样也可以使用goto进行跳转。 int main (void) int sum =0 sum= int s =0: for( int i =0: i 10: i++) s=S+1 goto here printf( sum %d\n", sum); here printf( here: \n"); printf sum = %d\n", sum); return 0: 33在宏定义中使用语句表达式 语句表达式的亮点在于定义复杂功能的宏。使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的岐义和漏 洞。下面就以一个宏定义例子,让我们来见识见识语句表达式在宏定义中的强悍杀伤力 假妇你此刻正在面试,面试职位是: Linux c语言开发工程师。面试官给你出了一道题 请定义一个宏,求两个数的最大值 别看这么简单的一个考题,面试官就能根据你写出的宏,来判断你的C语言功底,来决定给不给你Ofer 合格 对于学过C语言的同学,写出这个宏墓本上不是什么难事,使用条件运算符就能完成 #define MAX(x, y) x>y? x: y 这是最基本的C语言语法,如果连这个也写不出来,估计场面会比较尴尬。面试官为了缓解尴尬,一般会对你说:小伙子,你很棒,回去 等消息吧,有消息,我们会通知你!这时候,你应该明白:不用再等了,赶紧把这篇文章看完,接着面下家。这个宏能写出来,也不要觉 得你很牛X,因为这只能说明你有了C语言的基础,但还有很大的进步空间。比如,我们写一个程序,验证一下我们定义的宏是否正确

...展开详情
试读 94P 嵌入式C语言自我修养
立即下载 低至0.43元/次 身份认证VIP会员低至7折
    抢沙发
    一个资源只可评论一次,评论内容不能少于5个字
    • 分享达人

      成功上传6个资源即可获取
    关注 私信 TA的资源
    上传资源赚积分,得勋章
    最新推荐
    嵌入式C语言自我修养 50积分/C币 立即下载
    1/94
    嵌入式C语言自我修养第1页
    嵌入式C语言自我修养第2页
    嵌入式C语言自我修养第3页
    嵌入式C语言自我修养第4页
    嵌入式C语言自我修养第5页
    嵌入式C语言自我修养第6页
    嵌入式C语言自我修养第7页
    嵌入式C语言自我修养第8页
    嵌入式C语言自我修养第9页
    嵌入式C语言自我修养第10页
    嵌入式C语言自我修养第11页
    嵌入式C语言自我修养第12页
    嵌入式C语言自我修养第13页
    嵌入式C语言自我修养第14页
    嵌入式C语言自我修养第15页
    嵌入式C语言自我修养第16页
    嵌入式C语言自我修养第17页
    嵌入式C语言自我修养第18页
    嵌入式C语言自我修养第19页
    嵌入式C语言自我修养第20页

    试读已结束,剩余74页未读...

    50积分/C币 立即下载 >