数据操作
1 数据修饰 auto、static、register、const、volatile
- auto
auto关键字 修饰局部变量
auto修饰局部变量,标志这个局部变量是自动局部变量,自动局部变量分配在栈上。(也就是说如果不初始化,那么值就是随机的)
auto的局部变量就是默认定义的普通的局部变量,省略了auto关键字而已,
- static
static关键字可以修饰:局部变量、全局变量、函数
static修饰后改变了什么?
1.改变了生存周期
2.改变了作用域
static修饰不同对象时的作用:
1、局部变量:
局部变量就是在函数内定义的变量,普通的局部变量,生存周期是随着函数的结束而结束,
当用static修饰后,静态局部变量的生存周期就是当程序结束才会结束。
改变其生存周期的原因是被static修饰的局部变量被存放在.bss段或者.data段,而普通的局部变量是存放在栈上的。
总结:改变了生存周期,但是没有改变其作用域。
2、全局变量:
全局变量用static修饰改变了作用域,没有改变生存周期。
普通的全局变量是可以被其他的.c文件引用的,静态全局变量就只能被定义该全局变量的.c文件引用。
这样其他的文件就不能通过extern的方式去访问,这样主要是为了数据安全。
总结:改变其作用域,没有改变生存周期。
3、函数:
函数用static修饰,改变了作用域。
和静态全局变量一致
- register
register修饰的变量(一般是全局变量),编译器会尽量将它分配在寄存器中。(平时分配的一般是在内存中的)。
通过改善这个变量的访问效率可以极大地提升运行效率。读写效率会更高。
所以register修饰的变量用在那种变量被反复高频率的作用,
- const
const限定一个变量不允许被改变,产生静态作用。
可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
- volatile
volatile 关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问
2 大端模式、小端模式
大端字节存储模式 :
是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中
小端字节存储模式。
是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中
以0x1234为例进行说明。
内存操作
在一个C语言程序中,能够获取的内存就是三种情况:栈(stack)、堆(heap)、静态存储区
- 静态存储区
由 操作系统 分配 和 使用,有 .bss段 和 .data段组成,可读可写。
.data段(数据段)
已初始化的全局变量、静态变量存放在.data段。
.data段占用可执行文件空间,其内容有程序初始化。
.bss段
未初始化的全局变量、静态变量 和 初始化为0的全局变量、静态变量存放在.bss段。
.bss段不占用可执行文件空间,其内容由操作系统初始化。
- 栈 stack
函数执行时,函数的形参以及函数内的局部变量,分配在栈区,函数运行结束后,形参和局部变量去栈(自动释放)。
编译器在需要的时候分配,不需要时自动清除
1 动态内存管理(堆区 heap) malloc、calloc、realloc、free
开辟内存的函数
1、void* malloc(size_t size);
如果开辟成功,返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,所以使用malloc,要对返回值进行检查。
2、void* calloc(size_t num,size_ size);
函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0。
3、realloc
原本申请的空间不够,通过这个函数创建新的空间
释放动态开辟的内存
void free(void* ptr);
2 GCC对C的扩展:内存对齐 attribute、aligned()、packed()、at
GNU使用__attribute__选项来设置我们想要的内存字节对齐大小。__attribute__选项不属于标准C语言,它是GCC对C语言的一个扩展用法。
GCC支持用__attribute__为变量、类型、函数、标签制定特殊属性。
大致有六个参数值可以被设定,即:aligned, packed, transparent_union, unused, deprecated 和 may_alias 。
- aligned
该属性设定一个指定大小的对齐格式(以字节 为单位)
struct p
{
int a;
char b;
short c;
}__attribute__((aligned(4))) pp;
sizeof(a)+sizeof(b)+sizeof(c)=4+1+1=6<8 所以sizeof(pp)=8
struct m
{
char a;
int b;
short c;
}__attribute__((aligned(4))) mm;
sizeof(a)+sizeof(b)+sizeof(c)=1+4+2=7
a 后面需要用 3 个字节填充 才能和 b 是 4 个字节 一致
所以 a 占用 4 字节, b 占用 4 个字节,而 c 又要占用 4 个字节。所以 sizeof(mm)=12
- at
attribute( at(绝对地址) )的作用分两个,一个是绝对定位到Flash,另个一是绝对定位到RAM。
定位到flash中,一般用于固化的信息,如出厂设置的参数,上位机配置的参数,ID卡的ID号,flash标记等等
const u16 gFlashDefValue[512] __attribute__((at(0x0800F000))) = {0x1111,0x1111,0x1111,0x0111,0x0111,0x0111};
const u16 gflashdata__attribute__((at(0x0800F000))) = 0xFFFF;
定位到RAM中,一般用于数据量比较大的缓存,如串口的接收缓存,再就是某个位置的特定变量
//接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.
u8 USART2_RX_BUF[USART2_REC_LEN] __attribute__ ((at(0X20001000)));
1、绝对定位不能在函数中定义,局部变量是定义在栈区的,栈区由MDK自动分配、释放,不能定义为绝对地址,只能放在函数外定义。
2、定义的长度不能超过栈或Flash的大小,否则,造成栈、Flash溢出。
文件操作
1 文件 I/O 库
文件 I/O 指的是对文件的输入 / 输出操作,说白了就是对文件的读写操作
文件 I/O 是系统调用
1.1 文件描述符
文件描述符:是一个非负整数,是一个文件句柄,是与对应的文件绑定
文件描述符的分配:为没有被使用的且最小的非负整数
系统定义的文件描述符:0 1 2( 标准输入、标准输出、标准错位 )
1.2 读写操作(open 、close 、write 、read)
1、open("路径","flag方式")
open("路径","flag方式","权限")
flag方式
O_RDONLY: 只读方式
O_WRONLY: 只写方式
O_RDWR: 可读可写方式
O_CREAT: 不存在就创建
O_EXCL: 和 O_CREAT 搭配使用
权限
如果使用 O_CREAT,则需要加入第三个参数,设置文件的权限
2、close(文件描述符)
3、write(文件描述符,"写入的数据","数据大小")
4、read(文件描述符,"存储读取数据的缓冲区","数据大小")
1.3 文件 I/O 内核缓冲
调用 write() 只是将 字节数据拷贝到了 内核空间的缓冲区 ,拷贝完成之后函数就返回了, 在后面的某个时刻,内核会将其缓冲区中的数据写入(刷新)到磁盘设备中,所以由此可知,系统调用 write() 与磁盘操作并不是同步的,write()函数并不会等待数据真正写入到磁盘之后再返回。
- 控制文件 I/O 内核缓冲的系统调用
1、fsync()函数 文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回
2、fdatasync()函数 并不包括文件的元数据
3、sync()函数 将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中
2 标准I/O 库
标准C库中,用于文件I/O操作的库函数,叫做 标准I/O库
2.1 FILE 指针
FILE 指针的作用相当于文件描述符,只不过 FILE 指针用于标准 I/O 库函数中、而文件描述符则用于文件I/O 系统调用中
2.2 读写操作(fopen 、fclose 、fwrite 、fread)
- fopen()
1、fopen("路径","flag方式")
r 只读方式打开 O_RDONLY
r+ 可读可写方式 O_RDWR
w 只写方式打开(无文件则创建)
w+ 可读可写方式打开(无文件则创建)
a 只写方式打开(在文件尾写入,无文件则创建)
a+ 可读可写方式打开(在文件尾写入,无文件则创建)
2、fclose(FILE 指针)
3、fwrite(”写入数据“,每个字节大小,总大小,FILE 指针)
返回读取到的数据项的数目
4、fread(读出存储的数据缓冲区,单字节大小,总字节大小,FILE 指针)
返回写入的数据项的数目
5、fseek(FILE 指针,偏移量,SEEK_SET)
SEEK_SET 文件开头处
SEEK_END 文件末尾处
2.3 标准 I/O 的 stdio 缓冲
标准 I/O 在应用层维护了自己的缓冲,称为stdio 缓冲
- 控制 stdio 缓冲
1、setvbuf(FILE 指针,buf,缓冲类型,大小)
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
缓冲类型:
_IONBF 无缓冲,将立即调用文件 I/O 操作 write()或者 read()
_IOLBF 行缓冲,遇到换行符"\n"时,标准 I/O 才会执行文件
标准输入和标准输出默认采用的就是行缓冲模式
_IOFBF 全缓冲 在填满 stdio 缓冲区后才进行文件 I/O 操作(read、write)。
普通磁盘上的常规文件默认全缓冲模式。
2、fflush(stdout)
强制刷新将输出到 stdio 缓冲区中的数据写入到内核缓冲区
不常见的函数
终止进程 exit()、_exit()
这个参数用来表示进程终止时的状态,0表示正常终止,其余 表示非正常终止,
1、void exit(int status)
exit()是C语言库函数 头文件 <stdlib.h>
2、void _exit(int status)
_exit() 是**系统调用函数**
- exit()、return()的区别
exit()是一个库函数, return()是C语言的语句
exit() 函数最终会进入到内核,把控制权交给为内核,最终由内核去终止进程。
return并不会进入到内核,它只是一个main函数返回,返回到它的上层调用把控制权交给他的上层调用,最终由上层调用终止进程。