本博客参考《嵌入式Linux与物联网软件开发C语言内核深度解析》
一. C 语言与内存
对于单片机而言,C语言程序
内存是用来存储可变数据(变量)的,
常量存储在 flash 当中。
1. 对于内存:
无操作系统:通过编译器提供的
变量名来管理内存(编译器会将变量名与给其分配的内存首字节地址绑定),
函数名(指针)本质也是一个内存地址,
定义数组就是一次性定义一堆变量(第一个变量a[0]的地址记录在数组名a中),
结构体为聚合数据类型,一般传递结构体变量的指针来操作结构体。
有操作系统:
静态内存分配,
动态内存分配:使用 API,例如 malloc、free 接口
C++:new 对象分配内存,delete 删除对象
Java/C#:虚拟机
分类:静态内存 SRAM,动态内存 DRAM
特点:可以随机访问
三总线:地址总线、数据总线、控制总线(地址总线位数决定内存的大小 2^N 次方)
1 GB = 1024 MB 1 MB = 1024 KB 1 KB = 1024 B 1 B ( 字节 ) = 8 bit ( 位 )
内存编址以字节为单位
数据类型的本质是一个内存格子(存放数据值)的长度和解析方法
2. 栈 stack:
作用:用来保存非静态局部变量,将局部变量放入栈中(入栈),释放局部变量(出栈)(两操作由背后的运行时系统自动完成),在程序运行过程中跳转函数要通过栈来保存和恢复现场。
特点:小内存、自动化;先进后出;栈的大小可以设定的。
3. 堆 heap:
作用:需要的内存容量比较大时,动态申请存储大容量数据,比如链表。
特点:大内存、手工分配/使用/释放。
4. 静态存储区:
作用:存放静态局部变量和全局变量
特点:随程序运行而分配空间,直到程序运行结束才释放释放空间。
二. C 语言位操作
1. c 语言位操作:
位与 & :逐位相与
位或 | :逐位相或
位异或 ^ :不同为 1 ,相同为 0 。
位取反 ~ :按位取反。
左移位 << :左边移出,右边补零,x << n =x * 2^n
右移位 >> :右边移出,左边补零,x >> n = x / 2^n
2. 寄存器操作:
REG1 中的值为 0x AAA AAA AAA
特定位( 8 ~ 15 )清零用 & :
REG1 & = 0xFFFF00FF;
REG1 & = ~ 0x0000FF00;
特定位( 8 ~ 15 )置 1 用 | :
REG1 |= 0x0000FF00;
特定位( 8 ~ 15 )取反用 ^ :
REG1 ^= 0x0000FF00;
3. 位运算构建特定的数:
左移 << + 或 |
左移 << + 或 | + 取反 ~
4. 宏定义完成位运算:
置位:
#define SET_BIT_N(x,n) ((x) | (1 << ((n)-1)))
复位:
#define CLR_BIT_N(x,n) ((x) & ~(1 << ((n)-1)))
截取变量连续的部分:
#define GETBITS(x,n,m) ( (x & ~ (~(0U) << (m-n+1) ) << (n-1)) >> (n-1) )
三. 指针才是 C 语言的精髓
1. 指针定义
int a = 10;
int *p = &a; //将 a 的地址给指针 p,指针指向 a 变量。
*p = 20; //取指针 p 的值,并将其赋为 20,对应的 a 的值也被改为 20。
* 有两个作用,一是表示指针的级数,二是解引用(即取空间操作)。
& 为取地址符。
对于变量,左值一般是对应的写操作,右值对应读操作。
2. 野指针的产生原因:
1. 指针不是一个有效的地址。
2. 没有访问权限,对于字符串常量,只允许读操作,不允许写操作。
3. 内存越界。
防止野指针:要常将指针赋为 NULL,NULL 在 C++ 中为 0,在 C 语言里为 (void *) 0 ,为空指针。
3. const 修饰:
1. int const *p :指针 p 所指向的空间是常量,不能被修改,但 p 是可以被修改的。
2. int *const p :指针 p 所指向的空间是可以修改的,p 是不可以被修改的。
3. int const *const p :什么都不能修改。
对于 const 变量,可以通过指针的引用修改其值。
所谓的常量其实是防止常量区 .ro.data 里的用 const 进行修饰的变量。
4. 对于数组:
1. 数组名就是数组的首地址,它同样等于数组 0 取地址,即 buf = & buf[0]。
2. &buf 取到的还是数组的首地址,不过是首地址值,地址 +1,加的是地址值本身的数值加一。
3. 使用指针访问数组:
1. 利用下标访问。
2. 利用指针常量访问。
3. 利用指针变量访问,地址 +1,加的是一个数组元素空间的大小。
5. 强制类型转换
对于不同类型的数据,存储结构和方式,以及存储的空间大小会有所不同。
强制类型转换会:
1. 导致空间大小改变。
2. 导致数据的存储结构改变。
3. 对于指针,强制类型转换改变的是对空间的引用方式,一般是将引用的空间缩小,防止内存越位。
6. 数据的读写:
1. 根据变量名或直接利用地址,找到空间首地址。
2. 根据类型指定的空间的大小,从首字节地址开始,找出向后顺延后的空间的大小。
3. 按照类型数据存储格式的要求,进行数据的读写。
7. sizeof 与 strlen :
strlen和sizeof的区别是什么?不止是含义不一样,用法也不同
char str[]="hello";
sizeof(str) 结果为 6,包含 '\0' 字符
strlen(str) 结果为 5,去除了对 '\0' 的计算
char *p=str;
sizeof(*p) 结果为 1,*p 为 str[0] 的空间,大小为 1
strlen(*p) 结果为 5,计算 "hello" 的字符个数
int b[100];
sizeof(b) 结果为 400,计算数组空间
8. #define 与 typedef
#define 只进行简单的宏替换,预编译时被处理,可以实现类型组合,不可以构建新类型
typedef 可以给类型起别名,是编译时被处理,不可以实现类型组合,可以构建新类型
#include <stdio.h>
typedef unsigned char u8;
typedef char ch;
#define CH char
#define U8 unsigned char
//unsigned ch a = 100;
u8 a=255;
ch b = -128;
U8 c = 255;
CH d = -128;
unsigned CH e = 255;
int main()
{
printf("hello,a=%d,b=%d,c=%d,d=%d,e=%d\n",a,b,c,d,e);
}
9. 传参:
1. 普通传参:传入形参、传入指针
2. 传递数组:传入数组首字节地址,可用一维指针代替,但这样不直观
3. 传递结构体:成员值传递,成员地址传递,成员结构体传递,结构体地址传递
可以使用 const 来修饰传入的指针参数不可被修改
四. C 语言复杂表达式与指针高级应用
1. 指针数组
概念:放指针的数组
int *p[5] 为大小为 5 的 int * 类型的数组
2. 数组指针
概念:指向数组的指针
int (*p)[10] 为指向大小为 10 的 *p
3. 函数指针
概念:指向函数的指针
int (*pFunc)(int,int) 为指向返回值为 int 的含有两个 int 形参的函数