文章目录
嵌入式C语言
1 C语言预处理
-
包含头文件
#include
-
宏(单纯替换,不进行语法检查,最好加上括号)
#define 宏名 宏体
-
条件预处理(满足条件才执行)
#ifdef ABC printf("%s %s %d", __FUNCTION__, __FILE__, __LINE__); #endif //gcc 编译时可以加上-D动态注册宏定义
-
预定义宏
__FUNCTION__ : 函数名 __LINE__ : 行号 __FILE__ : 文件名 printf("%s %s %d", __FUNCTION__, __FILE__, __LINE__);
-
字符串化 #
-
连接符号 ##
#define ABC(x) #x //替换成"x" #define ABC(x) abc##x //替换成abcx
2 关键字及运算符
2.1 关键字:编译器预先定义了一定意义的字符串
-
数据类型
- char
- int
- long、short
- unsigned、signed
- float、double
- void
-
自动义类型
-
struct
-
union
-
enum(常量列表)
-
typedef(xxx_t)
typedef struct Student{ int id; char name[100]; char sex; }* PST, ST; // PST 等价于 struct Student *, ST等价于struct Student
-
-
逻辑结构
- if、else
- switch、case、default
- do、while、for
- continue、break、goto(函数内部进行跳转)
-
杂项
- return
- sizeof(其实也是运算符)
-
类型修饰符(资源属性中位置的限定)
-
auto
自动存储期,默认属性,自动变量,可读可写,如果再{ },栈空间
-
register
自动存储期,请求变量是寄存器的变量(不一定生效),访问和处理的速度更加快。
-
static
静态存储期,静态外部、静态内部、静态无链接。
-
const
常量的定义,只读的变量(实际可以根据某些方法修改,比如指针越界访问)。
-
extern
外部申明
-
volatile
易变的,告知编译器不要优化。
-
-
运算符
-
算术操作运算符
-
+、-
-
*、/、%
/* 0 % 3 = 0 1 % 3 = 1 2 % 3 = 2 3 % 3 = 0 4 % 3 = 1 n % m = res res的结果在[0, m-1]之间 利用这个特性 1.取一个范围的数: eg.给一个任意的数字,得到一个1到100以内的数字? (m % 100) + 1 ===>res; 2.得到M进制的一个个位数 eg.转化成16进制 3.循环数据结构的下标 eg.环形缓冲区 char buf[LEN]; 写数据: buf[w] = val; w = (w + 1) % LEN; 读数据: val = buf[R]; R = (R + 1) % LEN; 怎么判断空:R == W; 怎么判断满:(W + 1) % LEN == R; */
-
-
逻辑运算
-
||、&&(短路特性)
-
>、>=、<、<=
-
!
-
? :
-
-
位运算
-
<<、>>(编写代码过程尽量多用移位少用乘除)
-
&、|
/* A & 0 -----> 0 &:屏蔽 int a = 0x1234; a & 0xff00;屏蔽低8bit,取出高8bit A & 1 -----> A &:取出 &:清零器 |: A | 0 === A 保留 A | 1 === 1 设置为高电平的方法,设置set 设置一个资源的bit5为高电平,其它位不变 int a; a = (a | (0x1<<5)); =====>a |= (0x1<<n) 清除第五位 int a; a = a & ~(0x1<<5); ====> a &= (~(0x1<<n)) */
-
~、^
//^ 不引入新变量交换值 a = a ^ b; b = a ^ b; a = a ^ b;
-
-
赋值运算
- =
- +=、-=、&=
-
内存访问符号
- ()
- []
- {}
- ->、.
- &、*
-
3 C语言内存空间的使用
3.1 指针
内存类型资源地址、门牌号的代名词
-
指针+修饰符
-
const
const int *p;//常量指针,指向常量的指针。即P指向的内存可以变,P指向的数值内容不可变 int const *p;//同上 int* const p;//指针常量,本质是一个常量,而用指针修饰它。即P指向的内容不可变,但是P内存位置的数值可以变,硬件常用 const int* const p;//指向常量的常量指针。都不可变,寄存器
-
volatile
//防止优化指向内存地址,只跟硬件有关 volatile char *p;
-
typedef
-
-
指针+运算符
-
++、–、+、-
实际加减一个单位,自增(减)会更新该指针
-
[]
-
逻辑操作符
-
-
多级指针
3.2 数组
-
数组的定义
//一维数组名是一个常量符号(标签,指针常量),一定不要放到=的左边,自然也不能用自增(减) //编译器不会帮你检查是否越界,自己一定要注意。 int a[100]; a[-100];//可以,相当于首地址往下偏移100个单位
-
数组空间的初始化
/* 空间的赋值 按照标签逐一处理 数组空间的初始化 和 变量的初始化 本质不同,尤其在嵌入式的裸机开发中,空间的初始化往往需要库函数的辅助 int a[10] = {1,2,3}; 实际上,编译器帮你做了=======>a[0] = 1;a[1] = 2;a[2] = 3;a[3] = 0;... char 1.char buf[10]={'a','b','c'}; 定义普通变量没问题 如果要初始化字符串"abc",结尾一定要加上'\0'========>{'a','b','c','\0'}; 2.char buf[] = {"abc"};字符串最合理的写法,编译器看到""自动帮你加'\0' 3.char buf[] = "abc";更简单的写法 区分char *p = "abc"; 和 char buf[] = "abc"; p是直接指向常量区的字符串,但是buf是拷贝了一个常量区的副本通过标签逐一处理拷贝至数组空间 编译器只提供第一次初始化赋值,后面如果想变就只能逐一处理,为解决需求引入 char *strcpy(char *dest, const char *src);不安全,有内存泄露风险 char *strncpy(char *dest, const char *src, size_t n); 对于非字符空间 数据采集 char buf[10];-------->string unsigned char buf[10];--------->data 拷贝数据时,没有结束标志,只能定义个数 拷贝三要素:1.src 2.dest 3.个数 void *memcpy(void *dest, const void *src, size_t n); unsigned char buf[10]; unsigned char sensor_buf[100]; memcpy(buf, sensor_buf, 10*sizeof(unsigned char)); */
-
指针与数组
char *a[100]; sizeof(a) = 100*4;
-
多维数组
int b[5][6]; int **p = b;错误,二维数组和二维指针没有任何关系。 解析: b+1,一次移动6个int单位的数据 p+1,一次移动4个字节 应将p修改为======>int (*p)[6] 这样p每次移动也是6个单位的int 由此也可知道,二维数组的列很重要,初始化时,行可以空着,列必须有 int b[2][3][4]; int (*p)[3][4] = b;
3.3 结构体
-
字节对齐
效率,希望牺牲一点空间换取时间的效率 最终结构体的大小一定是4(32bit系统指针是4字节,除非遇到double,转成8的倍数)的倍数或者8(64bit系统指针是8字节)的倍数 结构体里成员变量的顺序不一致,也会影响到它的大小 eg.32位系统 struct abc{ char a; short e; int b; }; 4字节对齐,a填充三字节,余下的字节够e占两字节,指针可以一次读两个。b正好4字节,4+4=8. struct abc2{ char a; int b; short e; }; 4字节对齐,a占一字节,余下的三字节不够b存放,填充三字节,b占四字节,e占两字节,填充两字节(为后面的变量考虑)。4+4+4=12 //另一种对齐方式 1. 结构体变量中成员的偏移量必须是成员大小的整数倍 2. 结构体的最终大小必须是结构体最大简单类型的整数倍 struct xx { long long _x1; char _x2; int _x3; char _x4[2]; static int _x5; }; x1的偏移量是0,长度是8,符合; x2的偏移量是8,长度是1,符合; x3的偏移量是9,长度是4,不符合,需要在x2之后填充3字节使得x3的偏移量达到12; x4的偏移量是16,长度是2,符合; 此时总长度为(8)+(1+3)+(4)+(2)=18,而最大简单类型为long long长度为8,因此需要在x4之后再填充6字节,使得总长度达到24可被8整除
3.4 内存分布图
-
栈空间
内存的属性:1.大小 2.在哪里 内存分布图: 0xfffffff 内核空间 应用程序不许访问 -----------------------3G 运行时的栈空间 局部变量 ----------------------- 运行时的堆空间 malloc ----------------------- 全局的数据空间 static RW data(初始化的) bss(未初始化的) 只读数据段 "hello world" R text 代码段 code R text ----------------------- 0x0: 栈空间运行时,函数内部使用的变量,生存周期是函数内,一旦返回就释放 堆空间运行时,可以自由自我管理的分配和释放的空间,生存周期由程序员来决定。 只读空间认为是静态空间,程序编译时就确定,整个程序结束时释放内存,生存周期最长
-
堆空间
配对使用 分配多少,以什么方式读取,程序员决定 char *p; p = (char *)malloc(sizeof(int)*100); free(p);
-
只读空间
3.5 函数的使用
-
函数概述
函数三要素: 1.函数名(地址) 2.输入参数 3.返回值 在定义函数时,必须将3要素告知编译器 如何用指针保存函数? int (*p) (int, int, char);有了三要素p读内存的方法就知道了
-
输入参数
-
值传递
单纯的值拷贝,上层调用者保护自己空间值不被修改的能力
-
地址传递
上层调用者让下层子函数修改自己空间值的方式
1.修改某个值 int * short * long * int**(指针) 2.空间传递 2.1子函数看看空间里的情况 const * 2.2子函数反向修改上层空间里的内容 char*(字符串) void*(数据)
-
连续空间的传递(地址传递)
从头到尾,逐一遍历 数组 形参: int p[10] 只是为了告诉你这个数组的大小,但实际传递的还是指针。 void fun(int p[10]) =======> void fun(int *p) 结构体 使用地址传递,不仅节省空间,使用还方便。 为了避免对形参 char *p 的歧义 const char *p 只读 char *p 可修改 空间二要素:空间首地址、结束标志 字符空间 结束标志:内存里面存放了0x00(1B) 字符空间的标识符: char * 字符空间举例: int strcpy(char *dest, const char *src); 非字符空间 非字符空间0x00,不能当成结束标志,用数量(B)作为结束标志 数据空间(连续的)的标识符: void * 如果只是单纯修改某个值:那就用:具体类型(int) * 非字符空间举例:(首地址,数量缺一不可) void fun(void *buf, int len) { unsigned chr *tmp = (unsigned char *)buf;//void *只是形参化,最终的操作一定要把他转化成具体类型 int i; for(i = 0; i < len; i++) { tmp[i] = ; a = tmp[i]//+++++++++++------------ } }
-
-
返回值
-
基本语法
返回类型 函数名(参数列表) { return } 调用者,用值去接收 a = fun(); 被调者 int fun() { return num; } 都是拷贝的概念 返回类型 基本数据类型 指针类型(空间) 不可以返回数组
-
返回基本数据类型
需要两个返回值时 int * fun(int *p); 进行指针传递的时候要特别注意,自己的目的是否是值传递 void fun2(int *p); int main() { int *p; fun2(p);//这边是典型的值拷贝,传递了指针p的值 } 如果希望修改指针p所指向的地址空间,那么传递实参时,应该对p取地址,函数定义形参也该改为二维指针 即:void dun2(int **p); fun2(&p);
-
返回连续空间类型
指针作为空间返回的唯一数据类型 int *fun(); 地址:指向的合法性 作为函数的设计者,必须保证函数返回的地址所指向的空间是合法。【不是局部变量】 eg. char *fun(void) { char buf[] = "hello,world!"; return buf; } int main() { char *p; p = fun();//错误,fun被调用后,资源被释放,p指向的就是无效地址 } 使用者: int *fun(); int *p = fun(); 函数内部实现 1.静态区 2.只读区(不太常用) 3.堆区 eg. char *fun(void) { //static char buf[] = "hello world"; char *s = (char *)malloc(100);//一定记得释放 strcpy(s, "helloc world"); return s; } int main() { char *p; p = fun(); pritnf("%s", p); free(p); return 0; }
-