admin 管理员组文章数量: 887021
目录
一、C语言基础知识
C语言编译机制
1、宏定义与条件编译
2、运算符
3、指针
5、数组
6、内存管理
7、函数:
8、关键字:
9、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 $ gcc Hello.c // 使用gcc 编译 Hello.c 并且默认生成一个名为 a.out的可执行文件
2 $ gcc Hello.c ‐o haha // 使用gcc 编译 Hello.c ,并且指定生成名字为haha 的可执行文件
编译原理:
机器码(二进制):是处理器能直接识别的语言,不同的机器码代表不同的运算指令,不同的处理器机器码不同,所以机器码不可移植。
汇编语言:是机器码的符号化,即汇编就是用一个符号来代替一条机器码,所以不同的处理器汇编也不一样,即汇编语言也不可移植。(汇编语言和机器码一一对应的)
C语言:在编译时我们可以使用不同的编译器,将C源码编译成不同架构处理器的汇编,所以C语言可以移植。
2. 宏定义与条件编译
#define 是一个预处理命令,用来为一个字符串或者值定义一个符号常量。typedef是一个关键字,用于为已有类型定义新的类型别名。
无参宏 例如:#define PI (float)3.1415
带参宏 例如:#define MAX(a,b) a>b? a:b
无值宏 例如:#define DE_BUG //定义的时候不给值,只做“是否有定义”的判断用
条件编译:
形式1:
1 #ifdef // 判断某一个宏是否有定义
2
3 // 代码块
4
5 #endif // 判断语句的结束
形式2:
#if MACRO // 只要判断MACRO为非零值则表示条件为真
printf("__%s__%s__%d__\n", __FUNCTION__ , __FILE__ , __LINE__ );
#endif
.h 头文件格式:
1 #ifndef DEMO_H // 判断是否有定义某个宏 ,用来防止头文件被多次包含出现的重定义问题
2 #define DEMO_H //如果没有定义,说明前面没有包含过该头文件
3
4 //其它头文件
5 ....
6 // 结构体声明
7....
8 // 函数声明
9 ..
10 #endif // ifndef 的结束
要检查a是否定义:
#if defined a //检验a是否被定义
#undef a //用#undef语句解除定义
#define a 200 //重新定义a为200
‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’
#ifndef a //如果a没有被定义, #ifndef是if not defined的缩写
#define a 100
#endif
作用:表明这是C代码,要按照C语言的规则去解读,而不是按照C++的规则去解析,这样就可以保证C++代码正确的调用C代码。
#ifdef __cplusplus
extern "C"{
#endif
xxxxx //C语言的n个函数实现
#ifdef __cplusplus
}
#endif
3. 运算符
算数运算符:
运算符 |
术语 |
示例 |
结果 |
+ |
正号 |
+3 |
3 |
- |
负号 |
-3 |
-3 |
+ |
加 |
10 + 5 |
15 |
- |
减 |
10 - 5 |
5 |
* |
乘 |
10 * 5 |
50 |
/ |
除 |
10 / 5 |
2 |
% |
取模(取余) |
10 % 3 |
1 |
++ |
前自增 |
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的大小
int a = 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个元素的数组,每个元素类型为int |
int *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; // p指针变量无法修改,p指针指向地址的内容可以变化。
常目标指针:修饰的是指针所指向的目标,表示无法通过该指针来改变目标的数据。
char const * p ; 等价于 const char * p ;
5. 数组
只有在定义的时候赋值,才可以称为初始化。数组只有在初始化的时候才可以统一赋值。
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声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)。
在嵌入式系统编程中,将变量声明为volatile通常用于处理硬件寄存器或在不同线程或中断服务例程(ISR)之间共享的变量。它确保编译器不会优化掉对该变量的某些操作,确保其值始终从内存中读取而不是从缓存中读取,并且对变量的写入立即对代码的其他部分可见。
9. 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分别被标准输入、标准输出和标准错误占用了 )
open、write、read、lseek、close:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
打开文件或新建并打开
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);//创建文件时才传入mode-权限。
//返回值:打开失败 -1
//O_TRUNC标志:丢弃文件中的内容,文件大小变为0
//O_APPEND标志:保证每次调用 write()时都是从文件末尾开始写入。对read操作不会有影响。
把buf写入到文件描述符:
ssize_t write(int fd, const void *buf, size_t count);
//返回值:成功:返回写入的字节数 失败:-1
读取到buf:
ssize_t read(int fd, void *buf, size_t count);
关闭文件:
int close(int fd); //成功:0 失败:-1
调整读写位置偏移:
int lseek
本文标签: 语言
版权声明:本文标题:C语言与应用开发 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1729002091h1305506.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论