linux嵌入式C语言
课程链接: [史上最强最细腻的linux嵌入式C语言学习教程李慧芹老师]
0. gcc与vim的使用
gcc
指令
-Wall
:显示所有警告
gcc默认的警告包括真正的错误:error和 告警warning
执行过程
c源代码.c
-> 预处理^(E)^ -> 编译^(S)^ -> 汇编^©^.o
-> 链接 -> 可执行文件(默认a.out
)
带#都在预处理阶段完成
vim
vim的配置
默认全局参数在/etc/vim/vimrc
中,一般建议在家目录拷贝一份命名为.vimrc
,以生成只对本用户生效的配置,不干扰其他用户
快捷键
ctrl + p
可以补全声明过的代码
shift + k
跳转到函数MAN解释
vim * -p
以vim打开当前目录下所有文件(gt
)
1.基本概念
- 算法:解决问题的方法(流程图,NS图,有限状态机FSM)
- 程序:用某种语言实现算法
- 进程
- 防止写越界,防止内存泄漏,谁打开谁关闭,谁申请谁释放
编程注意事项
- 建议常用头文件
stdlib.h
- main函数建议用
exit(0)
替换return 0
- c语言中,如果一个函数没有找到其原型,编译器会 默认认为其返回int int * p= (int *) malloc(sizeof(int)),强转可能会掩盖问题^1^
- printf()是有返回值的
- 大段注释用
#if 0
和#endif
c
#if 0
被注释掉的内容
#endif
- 注释尽量用英文,防止中文标点符号乱入混淆
2. 数据类型,运算符和表达式
数据类型:(基本数据类型参考C语言数据类型)
基本数据类型
所占字节数
存储区别
不同类型的数据间进行转换 (隐式,显式->强制类型转换)
特殊性:
1. 布尔型bool
2. float类型
3. char型是否有符号
4. 不同形式的值:0,'0','\0'
5. 数据类型与后续代码中所使用的输入输出要相匹配
常规计数方式
- 254 表示十进制
- B1111110 表示二进制
- 0376 表示八进制
- 0xFE 表示十六进制
数据的存储
整型
整形的存储都是以补码 形式存储的
正数的补码是它本身 ,负数补码为其绝对值的补码取反+1
浮点型
浮点型的存储方式
表示为:0.314 * 10^1^
让整数部分为零,只在意精度部分(小数部分) 和 指数部分
如图float类型为例,0-22位表示小数部分 ,22-30位表示指数 ,31表示符号位
double类型多出的32位全部用于精度控制
精度问题
- float只表示一个大概的范围,是一个近似值
- 一个浮点型不可能绝对的等于一个整型
浮点型有位数限制,不可能无限精确
c
float f=1;
int Fun(float f)
{
if(f < 0) return 1;
else if(if==0) return 0;
else return -1;//结果返回-1
}
解决方法:
c
fabs(f-0)<=1e-6 //等价于f==0
char型
char型也以补码 的形式存储
c语言中默认char有无符号是不确定的
bool类型
- 头文件
stdbool.h
数据类型转换
- 小转大精度增加数据不丢失,大转小丢失数据且不会进行四舍五入
- 注意
%d
是按照有符号整型打印的,可能会丢失数据
隐式转换
不同数据类型进行运算,默认向精度比较高的数据类型靠拢
c
int i;
float f
i + f -> res
//int float -> float
显式转换(强制类型转换)
变量与常量
常量:在程序执行过程中值不会发生变化的
分类:整型常量,实型常量,学符常量,学符常量,标识常量
整型常 :1,790,76,52
实型常量:3.14,5.26 1.9999
字符常量:由单引号引起来的单个的字符或转义字符,如'a','\n'
字符串常量:由双引号引起来的一个或多个字符组成的序列,如:"a"
标识常量:`#define 宏名 宏体` ,在程序执行过程中用宏体替换宏名,占用编译时间,不检查语法,只是单纯替换
变量:用来保存一些特定内容,并且在在程序执行过程中值随时可能变化的量
注意:
- '\015':把该数当八进制看
- '\x7f': 把该数当作十六进制看
""
:空串,只有一个\0
\018
因为八进制里不可能出现8,所以表示\0和1和8
宏
- 最基础用法
#define PI 3.14
- 表达式
#define MAX(a,b) a>b?a:b
当传入MAX(i++,j++)时,比较时错误,大的那一项会自增两次,解决方法:用变量接收
解决:
c
#define MAX(a,b) \
({typeof(a) A=a,B=b;((A)>(B)?(A):(B));})//非标准c
//用a的类型定义A,B,类似c++里的auto和decltype
errno
[C 库宏 - errno](https://www.runoob.com/cprogramming/c-macro-errno.html)
perror()
c
void perror(const char *str)
在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和errno所对应的错误一起输出。
定义
TYPE NAME =VALUE;
标识符:由字母,数字,下划线组成不能以数字开头的一个表示序列
要求写标识符尽量见名生义
数据类型:基本数据类型+构造类型
存储类型:auto static register extern(说明型)
- auto:默认,自动分配空间,自动回收空间
- register :建议型,寄存器类型(既少又快)
大小有限制,只能定义32位大小的数据类型,寄存器没有地址,所以一个寄存器类型的变量无法打印出地址查看或使用
- static:静态型,自动初始化为0或空值,存放在静态区,生命周期和程序一致,并且其变量的值有继承性,只定义一次,防止变量/函数对外扩展,常用于声明变量和函数,如果想在外部进行调用,需要get函数
- extern:说明型,意味不能改变被说明的变量的值
变量的生命周期和作用和范围
- 全局变量作用与定义开始知道程序结束
- 变量定义遵循就近原则
- 全局变量使用要谨慎(errno要马上打印,否则可能不是预期值)
运算符和表达式
- 表达式与语句的区别
- 运算符部分:
- 每个运算符所需要的参与运算的操作数个数
- 结合性
- 优先级
- 运算符的特殊用法
- 位运算的重要意义
tips:
- %:左右必须为整型
c
printf("1 divide 2 equals : %d",1/2);//结果为 0
运算符
1.自增自减运算符
赋值表达式返回值为所赋值的对象
c
char a;
char ret=(a='\0');
printf("a='\\0' 表达式的返回值是: %c\n",ret);//ret='\0'
- 逻辑与-短路特性
c
int m=1,n=1,a=1,b=2,c=3,d=4;
(m=a>b)&& (n=c>d)
只有
&&
左边为真才会执行右边语句,如果左边为假,不执行右边语句
强制类型转换
- 只是中间过程,不会让被强转的数赋值
位运算
3. 输入,输出专题
input & output -> I/O
- 标准IO(系统调用IO)
- 文件IO
1. 格式化的输入输出函数:scanf,printf
printf:
"printf(%[修饰符]格式字符",输出表项)
~
- %f 默认输出保留六位小数,不够补零
- 注意数据溢出(加单位)
- 注意加上
\n
,牵扯到缓冲区^2^,\n
能刷新缓冲区 - 返回值是打印个数+1
scanf:
scanf有返回值,把scanf放到循环内部一定要慎重,注意核验scanf返回值,否则可能无法接收到有效内容
- %s 是比较危险的,因为不知道存储空间大小
2. 字符串输入输出函数:getchar
,putchar
抑制符:例如两个连续换行输入,用getchar吃掉换行符
出错都返回EOF
3. 字符串输入输出函数:gets
(危险),puts
gets
危险原因:
- 不会进行溢出检查
标准库替代品--fgets()
治标不治本,不一定保证我们所输入的字符串都能完整输出,只是保证安全性
"方言"替代:GNC库提供 getline()
puts
输出自带换行符
4. 流程控制
C语言三大结构
- 1 顺序,2 选择,3 循环
- 语句逐步执行
- 出现了一种以上情况
- 某个条件成立的情况下,重复执行某个动作
简单结构与复杂结构:自然流程
NS图,流程图 工具 - " Dia "
养成好习惯
-
先理清结构
-
慎用
goto
goto使用的是无条件的跳转,且不能跨函数调整,会影响代码的结构性
5.数组
- 定义:【存储类型】 数据类型 标识符[下标]
- 特点:在内存中连续存放
6.指针
数组名是地址常量,不能放在等号左边
c
char str[]="hello";
str="world";//false
解决方式:strcpy(str,"world")
;
c
char *str="hello";
strcpy(str,"hello");//false 因为字符指针指向了一个串常量,不允许修改
str="world";//true 可以让它指向另一块区域
const
指针常量:指向的值能修改,指针方向不可以修改
常量指针:指向的值不能修改,指针方向可以修改
7.函数
函数与一维数组
传参[]
等价于*
函数与二维数组
- 传首地址相当于视作大数组
- 传参
[]
等价于*
但是第二个[N]不能转换,因为它说明了数组列的大小
例如 :void print(int [][N],int row,int column);
或者 void print(int (*p)[N],int row,int column);
c
#include <stdlib.h>
#include <stdio.h>
#define COLUMN 4
//int *a[COLUMN]是错误的,因为这样表示的是int **a
void print_arr(int (*a)[COLUMN],int R,int C)//表示指向数量为4的数组的指针
{
for (size_t i = 0; i < R; i++)
{
for (size_t j = 0; j < C; j++)
{
printf("%d ",a[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[][COLUMN]={1,2,3,4,5,6,7,8,9,10,11,12};
print_arr(arr,3,COLUMN);
exit(0);
}
函数与字符数组
7.结构体
内存/地址 对齐
-如果要求不让编译器对齐 ,结构体尾部加上宏:__attribute__(packed)
结构体传形参开销大,一般传指针
共用体
- 变量不能共存
- 共用一块空间
- 空间占用以最大为准
位域
大小端
"大小端"是指计算机系统中对多字节数据类型(如16位、32位或64位整数)存储时字节顺序的一种约定。主要分为两种不同的字节顺序格式:
- 小端模式 (Little Endian):---x86
在小端模式中,低序字节(即最低有效字节,LSB)被存放在内存中的低地址处,而高序字节(即最高有效字节,MSB)则被存放在内存中的高地址处。
例如,对于一个16位的值0x1234,在小端模式下会被存储为:
内存地址低字节:0x34
内存地址高字节:0x12 - 大端模式 (Big Endian):---51单片机
在大端模式中,高序字节被存放在内存中的低地址处,而低序字节则被存放在内存中的高地址处。
例如,对于同一个16位的值0x1234,在大端模式下会被存储为:
内存地址低字节:0x12
内存地址高字节:0x34
嵌套
- 结构体共用体一种用法:把32位分成高位低位
c
union
{
struct
{
uint16_t i;
uint16_t j;
}x;
uint32_t y;//无符号32位整型
}a;
a.y=0x11223344;//32位整型
printf("%x\n",a.x.i+a.x.j);//打印4466
因为共用体共用一块连续空间,32位占用的空间正好分配给两个十六位整型
枚举
常当作宏使用,不会在预处理阶段被替换
8. 动态内存管理
原则:谁申请谁释放
free只是不把指针分配给那块空间,不是把空间清空
如果free后再次对原指针赋值,会造成野指针,十分危险
所以一般在free后立刻把原指针置空
void*
windows环境下:void* 必须转成接收类型 才能接受
linux环境下:void*可以直接接收
指针作函数参数
指针传参证明
- 指针传参,传的是形参
c
void Func(int * p)
{
printf("passing_p`s adress is :%p\n",&p);
p=(int *)malloc(sizeof(int));
*p=6;
}
int main()
{
int *p;
Func(p);
if(p==NULL)
{
printf("p为空指针,赋值错误\n");
return 0;
}
printf("p`s adress is :%p\n",&p);
free(p);
return 0;
}
结果:passing_ps adress is :0061FF00 ps adress is :0061FF1C
二者地址不同,非同一指针
结果: 段错误^3^
原因:main函数里的p没有分配内存,证明函数里对p的操作没有影响到p本身
c
void Func(int * p)
{
printf("passing_p`s adress is :%p\n",p);
*p=6;
}
int main()
{
int *p;
Func(p);
if(p==NULL)
{
printf("p为空指针,赋值错误\n");
return 0;
}
printf("p`s adress is :%p\n",p);
free(p);
return 0;
}
结果:passing_p adress is :00213000 ; p adress is :00213000
二者指向地址一样,为同一数据
总结:形参都是值传递。但是这个值如果是指针的话,是可以改变指针指向内容的值,即实参的值。这个要弄清两个概念:指针和指针指向的数据。
解决方法:
-
传指针的地址
结果:
成功
-
返回值返回
9.重定义 typedefine
- 和宏的区别
c
#define int* P
typedef int* p;
P a1;//a1是int *
p a2;//a2是int *
//-----------------
P b1,b2;//b1是int *,b2是int
p c1,c2;//C1,c2都是int *
10.Makefile
工程管理器
Makefile的基本格式
bash
target1 ...: prerequisites ...
command # 注意在命令前面需要添加一个Tab键
...
target2 ...: prerequisites ...
command
...