ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C语言与应用开发

C语言与应用开发 目录一、C语言基础知识1. C语言编译机制2. 宏定义与条件编译3. 运算符4. 指针5.数组6. 结构体6.内存/外存7. 函数:8. 关键字:9. 物理地址和虚拟地址10. Linux 下的文件类型二、嵌入式应用开发:系统调用、库函数:文件IO(系统IO):文件描述符:open、write、read、lseek、close:​编辑Linux下文件管理:错误编号errno、strerror()、perror():空洞文件:文件描述符和文件的关系:文件描述符的复制:文件共享:原子操作与竞争冒险:截断文件 truncate():fcntl()和ioctl():标准IO:标准I/O和文件I/O的区别:fopen、fread、fwrite、fseek、ftell():检查或复位状态:格式化输出和输入:I/O 内核缓冲:stdio缓冲:文件描述符与FILE指针互转:文件属性和目录:文件类型-7类:获取文件属性-stat函数:软链接和硬链接:目录:字符串处理字符串输出/输入:系统信息和系统资源:系统信息和时间获取:进程时间(cpu时间):信号:信号的分类:进程对信号的处理:向进程发送信号:信号集:信号掩码:实时信号:进程:进程的相关概念:进程相关的函数:环境变量:fork()创建子进程:进程状态与进程关系:守护进程:进程间通信IPC:通信目的:如何通信:通信方法:1、管道2、信号3、内存映射4、共享内存5、信号量6、socket套接字7、消息队列线程:概念与特点:创建线程:终止线程:回收线程:取消线程:分离线程:线程同步:互斥锁:条件变量:信号量:网路编程:基本概念知识:UDP框架:TCP框架一、C语言基础知识1. C语言编译机制1)预处理:处理源代码文件中的以“#”开始的预编译指令,删除所有注释“//”。2)编译:编译成汇编代码。3)汇编:汇编器将汇编代码转变成机器可以执行的二进制指令。4)链接:将各个目标文件链接起来生成最终的可执行文件。预处理 gcc -E a.c -o a.i 编译 gcc -s a.i -o a.s 汇编 gcc -c a.s -o a.o 链接 gcc a.o -o a //gcc a.c -o a编译原理:机器码(二进制):是处理器能直接识别的语言,不同的机器码代表不同的运算指令,不同的处理器机器码不同,所以机器码不可移植。汇编语言:是机器码的符号化,即汇编就是用一个符号来代替一条机器码,所以不同的处理器汇编也不一样,即汇编语言也不可移植。(汇编语言和机器码一一对应的)C语言:在编译时我们可以使用不同的编译器,将C源码编译成不同架构处理器的汇编,所以C语言可以移植。2. 宏定义与条件编译#define 是一个预处理命令,用来为一个字符串或者值定义一个符号常量。typedef是一个关键字,用于为已有类型定义新的类型别名。无参宏 例如:#define PI (float)3.1415带参宏 例如:#define MAX(a,b) ab? a:b无值宏例如:#define DE_BUG //定义的时候不给值,只做“是否有定义”的判断用条件编译:形式1:1#ifdef // 判断某一个宏是否有定义23 // 代码块45 #endif // 判断语句的结束形式2:#if MACRO // 只要判断MACRO为非零值则表示条件为真printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );#endif .h头文件格式:1 #ifndef DEMO_H // 判断是否有定义某个宏 ,用来防止头文件被多次包含出现的重定义问题2 #define DEMO_H //如果没有定义,说明前面没有包含过该头文件34 //其它头文件5 ....6 // 结构体声明7....8// 函数声明9 ..10 #endif // ifndef 的结束要检查a是否定义:#if defineda//检验a是否被定义#undefa//用#undef语句解除定义#definea200//重新定义a为200‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’#ifndefa //如果a没有被定义, #ifndef是if not defined的缩写#definea100#endif作用:表明这是C代码,要按照C语言的规则去解读,而不是按照C++的规则去解析,这样就可以保证C++代码正确的调用C代码。#ifdef __cplusplusextern "C"{#endifxxxxx //C语言的n个函数实现#ifdef __cplusplus}#endif3. 运算符算数运算符:运算符术语示例结果+正号+33-负号-3-3+加10 + 515-减10 - 55*乘10 * 550/除10 / 52%取模(取余)10 % 31++前自增a=2; b=++a;a=3; b=3;++后自增a=2; b=a++;a=3; b=2;--前自减a=2; b=--a;a=1; b=1;--后自减a=2; b=a--;a=1; b=2;赋值运算符:运算符术语示例结果=赋值a=2; b=3;a=2; b=3;+=加等于a=0; a+=2;a=2;-=减等于a=5; a-=3;a=2;*=乘等于a=2; a*=2;a=4;/=除等于a=4; a/=2;a=2;%=模等于a=3; a%=2;a=1;逻辑运算符:与或||非!4. 指针内存地址(编号):编码就是对内存的每一个字节(8位)分配一个32位或64位的编号(与32位或者64位处理器相关)。 这个内存编号我们称之为内存地址。char:占一个字节分配一个地址int:占四个字节分配四个地址指针概念:指针的实质就是内存“地址”。指针就是地址,地址就是指针。指针是内存单元的编号,指针变量是存放地址的变量。指针也是一种数据类型,指针变量也是一种变量。l指针变量指向谁,就把谁的地址赋值给指针变量。l“*”操作符操作的是指针变量指向的内存空间。取目标。可以取得一个变量在内存中的地址,但不能取寄存器变量。使用sizeof()测量指针的字节大小,得到的总是:4(32位系统)或8(46位系统)指针变量加减:如果是一个int *,+1的结果是增加一个int的大小inta= 10;int*p= a;//一级指针,p是指针变量*p= 100;//*p==a,a=100二级指针int**q= p;//可理解为int*(*q)= p*q就是p//**q就是a定义说明int i定义整形变量int *p定义一个指向int的指针变量int a[10]定义一个有10个元素的数组,每个元素类型为intint *p[10]定义一个有10个元素的数组,每个元素类型为int*int func()定义一个函数,返回值为int型int *func()定义一个函数,返回值为int *型int **p定义一个指向int的指针的指针,二级指针数组指针:专门用来指向数组的指针。int (* p) [5] ;//定义一个名为p的指针,指向的类型为int [5]指针数组:专门用来存放指针的数组。int * p[5] ; //定义一个名字为p的数组,并且确定该数组中用来存放int *整形地址。指针与数组:int arr[5] = {1,2,3,4,5};int *p1 = arr[0] ; //等价与int *p2= arr;指针p指向数组的首地址。(数组名arr只代表数组首元素的地址)指针的万能拆解方法:对于任何的指针都可以分为两部分:第一部分:说明他是一个指针(*p)第二部分:说用它所指向的内容的类型(*p)以外的东西void型指针:是没有办法直接索引目标的。必须先进行强制类型转换在使用。修饰指针,表示该指针指向了一个未知类型的数据。修饰函数的参数列表, 则表示该函数入参不需要参数。修饰函数的返回值,则表示该函数没有返回值。const指针:常目标指针:修饰的是指针所指向的目标,表示无法通过该指针来改变目标的数据。char const * p ; 等价于const char * p ;声明指针可修改指针指向地址的内容可修改适用场景char* s✅✅指向可写字符串const char* s✅❌指向只读字符串(如getenv返回值)char* const s❌✅常指针;指针固定,内容可修改const char* const s❌❌完全只读5.数组只有在定义的时候赋值,才可以称为初始化。数组只有在初始化的时候才可以统一赋值。char buffer[5];memset(buffer, '\0', sizeof(buffer)); //初始化字符数组。'\0'表示空字符,它的ASCII值为0,作为字符数组的结束标志。6. 结构体第一种方式(匿名结构体):// 定义一个结构体类型,结构体类型名为BatteryClusterData typedef struct { int frame1; int frame2; int frame3; } BatteryClusterData; // 声明变量 BatteryClusterData cluster1; // 只能在声明时,赋值初始化 BatteryClusterData cluster2 = {1, 2, 3}; // 错误用法!匿名结构体没有标签 struct BatteryClusterData cluster3;第二种方式(带标签结构体):// 定义了一个命名结构体BatteryClusterData,并使用typedef为它起了别名BMS_ClusterData typedef struct BatteryClusterData { int frame1; int frame2; int frame3; } BMS_ClusterData; // 起的别名也可以和结构体名一样。 声明方式多: // 使用别名声明 BMS_ClusterData cluster1; // 使用完整标签声明 struct BatteryClusterData cluster2;6.内存/外存RAM(Random Access Memory,随机存取存储器)定义:RAM是一种易失性存储器,能够随机访问存储在其中的数据。当电源关闭时,RAM中的数据会丢失。分类:SRAM(Static Random Access Memory,静态随机存取存储器):不需要刷新电路即能保存数据,存取速度快,但价格较高,通常用于CPU的缓存。DRAM(Dynamic Random Access Memory,动态随机存取存储器):需要定期刷新以维持数据,存取速度相对较慢,但价格较低,是计算机内存的主流。DRAM进一步分为SDRAM、DDR SDRAM等。ROM(Read-Only Memory,只读存储器)定义:ROM是一种非易失性存储器,数据在制造时写入,之后通常不能更改。注意:传统上ROM是只读的,但现代技术如Flash等已经模糊了ROM和RAM之间的界限。nand flash已代替ROM。Flash定义:Flash是一种非易失性存储器,掉电不丢失,结合了RAM和ROM的优点。分类:NOR Flash:适用于小容量、快速随机访问的场景,如BIOS、路由器系统等。NAND Flash:写入性能好,大容量下成本低,广泛应用于手机、SSD等。NAND Flash进一步分为SLC、MLC、TLC等类型,根据每个存储单元存储的比特数不同而区分。存储器结构由块(Block)和页(Page)组成,每个块包含多个页。擦除操作以块为单位进行。eMMC是一种集成了闪存芯片和控制器的储解决方案,其核心是NAND Flash。内存:内部存贮器,暂存程序/数据——掉电丢失SRAM、DRAM、DDR、DDR2、DDR3。内存是沟通CPU与硬盘的桥梁:暂存放CPU中的运算数据;暂存与硬盘等外部存储器交换的数据。外存:外部存储器,长时间保存程序/数据—掉电不丢ROM、ERRROM、FLASH(NAND [eMMc]、NOR)、硬盘、光盘。大端序(Big-Endian)和小端序(Little-Endian):是两种计算机存储字节序的方式。在计算机中,数据的存储是以字节为单位的,每个字节由8个二进制位组成。对于多字节的数据类型(如整数、浮点数等),计算机需要决定如何将其存储在内存中。大端序是指将数据的高位字节存储在低位地址,低位字节存储在高位地址。小端序则相反,将数据的低位字节存储在低位地址,高位字节存储在高位地址。(网络字节序指的就是大端序)例如: 数字int 258的16进制形式为0x00000102,二进制形式为:00000000 00000000 00000001 00000010,其在计算机内存中的小端序存储方式如图所示:7. 函数:静态函数:缩小可见范围,减少与其它文件中同名函数冲突的问题。静态函数一般会定义在头文件中, 然后被需要使用该函数的源文件包含即可。1 static int func (int a, int b )2 {3 // 函数体4 }递归函数:概念: 如果一个函数的内部,包含了对自己的调用则称为递归函数。8. 关键字:static:静态变量都在全局数据区分配内存。静态全局变量在声明它的整个文件都是可见的,而在该文件之外是不可见的。局部变量所在函数每次调用的时候都会被重新分配存储空间,函数结束后,就会回收该存储空间。静态局部变量不会,始终保持当前值。extren:用来声明全局变量的,表明变量或者函数是定义在其他其他文件中的。用来修饰外部全局变量,表示该变量在其他文件中定义。首先讲一下,声明与定义概念:声明不等于定义,声明只是指出了变量的名字,并没有为其分配存储空间;定义指出变量名字同时为变量分配存储空间,定义包含了声明。注意:在程序中一个变量可以声明多次,但只能定义一次。volatile:提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,告诉编译器对该变量不做优化,都会直接从变量内存地址中读取数据,从而可以提供对特殊地址的稳定访问。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)。环境共享变量修饰方式根本原因单片机/RTOS裸机volatile单核,只需解决“编译器优化”问题。被中断修改的全局变量;硬件寄存器(外设地址)Linux 多线程互斥锁(Mutex)或原子操作(Atomic)多核,需解决“CPU缓存一致性”与“指令重排序”。volatile在Linux别用。9. 物理地址和虚拟地址物理地址 (Physical Address):实际硬件地址,内存条上每个字节的真实地址唯一且有限:由硬件决定,比如4GB内存的地址范围是 0x00000000 - 0xFFFFFFFF直接对应硬件:CPU通过物理地址直接访问内存虚拟地址 (Virtual Address):每个进程都有完整的地址空间(32位是4GB,64位更大)。由操作系统管理:通过MMU(内存管理单元)映射到物理地址。在Linux中,虚拟地址空间被划分为用户空间和内核空间。关系:操作系统为每个进程维护一个页表,页表定义了虚拟地址到物理地址的映射。当进程访问一个虚拟地址时,MMU通过查找页表将其转换为物理地址,然后访问物理内存。这种机制使得进程之间的内存隔离成为可能,同时允许操作系统更灵活地管理内存。为什么需要虚拟地址?进程隔离:每个进程有自己的地址空间,互不干扰内存保护:防止进程访问其他进程或内核的内存简化编程:程序员不用关心物理内存布局内存扩展:可以使用交换空间(swap)扩展可用内存mmap函数:mmap函数将一个文件或者设备映射到进程的虚拟地址空间。这样,进程就可以通过指针来访问文件内容,而不是使用read/write函数。#include sys/mman.h void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 参数说明: addr: 建议的映射地址(通常为NULL,让系统自动选择) length: 映射区域长度(单位字节) prot: 保护权限 PROT_READ - 可读 PROT_WRITE - 可写 PROT_EXEC - 可执行 PROT_NONE - 不可访问 flags: 映射类型 MAP_SHARED - 共享映射(修改会写回文件) MAP_PRIVATE - 私有映射(写时复制) MAP_ANONYMOUS - 匿名映射(不关联文件) fd: 文件描述符 offset: 文件偏移量10. Linux 下的文件类型1,普通文件(regular):存在于外部存储器中,用于存储普通数据。2,目录文件(directory):用于存放目录项,是文件系统管理的重要文件类型。3,管道文件(pipe):一种用于进程间通信的特殊文件,也称为命名管道 FIFO。4,套接字文件(socket):一种用于网络间通信的特殊文件。只能使用系统io访问。5,链接文件(link):用于间接访问另外一个目标文件,相当于 Windows 快捷方式。6,字符设备文件(character):字符设备在应用层的访问接口。只能使用系统io访问。7,块设备文件(block):块设备在应用层的访问接口系统IO (没有设置缓冲区)标准库IO目录操作库文件10二、嵌入式应用开发:通过调用内核提供的系统调用函数或使用C 库函数来开发具有相应功能的应用程序。系统调用、库函数:系统调用:Linux操作系统向应用层提供的接口,是Linux应用层进入内核空间的入口。比如open、write、read、close 等。C库函数:一部分是对系统调用函数封装而来,在效率和使用便利性方面有提升。一部分是为调用系统函数的而编写的函数。区别:库函数是属于应用层,而系统调用是内核提供给应用层的编程接口,属于系统内核的一部分;库函数运行在用户空间,调用系统调用会由用户空间(用户态)陷入到内核空间(内核态);库函数通常是有缓存的,而系统调用是无缓存的,所以在性能、效率上,库函数通常要优于系统调用;可移植性:库函数比系统调用具有更好的可移植性。通常对于不同的操作系统,其内核向应用层提供的系统调用往往都是不同,譬如系统调用的定义、功能、参数列表、返回值等往往都是不一样的。文件IO(系统IO):文件描述符:概念:文件描述符是文件句柄、一个非负整数 int类型,与对应的文件相绑定。如何分配:每次分配一个没有被使用的最小非负整数作为文件描述符。如3、4等等。(其中0、1、2分别被标准输入、标准输出和标准错误占用了 )
返回列表