• C语言进阶


    数据操作

    1 数据修饰 auto、static、register、const、volatile

    • auto

    auto关键字 修饰局部变量

    auto修饰局部变量,标志这个局部变量是自动局部变量,自动局部变量分配在栈上。(也就是说如果不初始化,那么值就是随机的)

    auto的局部变量就是默认定义的普通的局部变量,省略了auto关键字而已,

    • static
    static关键字可以修饰:局部变量、全局变量、函数
    
    • 1
    static修饰后改变了什么?
    1.改变了生存周期 
    2.改变了作用域 
    
    • 1
    • 2
    • 3

    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

    开辟内存的函数 
    
    1void* malloc(size_t size); 
     如果开辟成功,返回一个指向开辟好空间的指针。
     如果开辟失败,则返回一个NULL指针,所以使用malloc,要对返回值进行检查。 
    
    2void* calloc(size_t num,size_ size);
     函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0。
     与函数malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化为全03、realloc 
     原本申请的空间不够,通过这个函数创建新的空间
    
    释放动态开辟的内存
    
    void free(void* ptr); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 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;
    
    • 1
    • 2
    • 3
    • 4
    定位到RAM中,一般用于数据量比较大的缓存,如串口的接收缓存,再就是某个位置的特定变量
    
    //接收缓冲,最大USART_REC_LEN个字节,起始地址为0X20001000.
    u8 USART2_RX_BUF[USART2_REC_LEN] __attribute__ ((at(0X20001000)));
    
    • 1
    • 2
    • 3
    • 4

    1、绝对定位不能在函数中定义,局部变量是定义在栈区的,栈区由MDK自动分配、释放,不能定义为绝对地址,只能放在函数外定义。
    2、定义的长度不能超过栈或Flash的大小,否则,造成栈、Flash溢出。

    文件操作

    1 文件 I/O 库

    文件 I/O 指的是对文件的输入 / 输出操作,说白了就是对文件的读写操作
    文件 I/O 是系统调用

    1.1 文件描述符

    文件描述符:是一个非负整数,是一个文件句柄,是与对应的文件绑定

    文件描述符的分配:为没有被使用的且最小的非负整数

    系统定义的文件描述符:0 1 2( 标准输入、标准输出、标准错位 )

    1.2 读写操作(open 、close 、write 、read)
    1open("路径""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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    1.3 文件 I/O 内核缓冲

    调用 write() 只是将 字节数据拷贝到了 内核空间的缓冲区 ,拷贝完成之后函数就返回了, 在后面的某个时刻,内核会将其缓冲区中的数据写入(刷新)到磁盘设备中,所以由此可知,系统调用 write() 与磁盘操作并不是同步的,write()函数并不会等待数据真正写入到磁盘之后再返回。

    • 控制文件 I/O 内核缓冲的系统调用
    1fsync()函数  文件的内容数据和元数据写入磁盘,只有在对磁盘设备的写入操作完成之后,fsync()函数才会返回
    
    2fdatasync()函数 并不包括文件的元数据	
    
    3sync()函数 将所有文件 I/O 内核缓冲区中的文件内容数据和元数据全部更新到磁盘设备中
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2 标准I/O 库

    标准C库中,用于文件I/O操作的库函数,叫做 标准I/O库

    在这里插入图片描述

    2.1 FILE 指针

    FILE 指针的作用相当于文件描述符,只不过 FILE 指针用于标准 I/O 库函数中、而文件描述符则用于文件I/O 系统调用中

    2.2 读写操作(fopen 、fclose 、fwrite 、fread)
    • fopen()
    1fopen("路径""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 文件末尾处
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    2.3 标准 I/O 的 stdio 缓冲

    标准 I/O 在应用层维护了自己的缓冲,称为stdio 缓冲

    • 控制 stdio 缓冲
    1setvbuf(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)。
           普通磁盘上的常规文件默认全缓冲模式。
    
    2fflush(stdout)
    	强制刷新将输出到 stdio 缓冲区中的数据写入到内核缓冲区
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    不常见的函数

    终止进程 exit()、_exit()

    这个参数用来表示进程终止时的状态,0表示正常终止,其余 表示非正常终止,

    1void exit(int status)
    exit()是C语言库函数 头文件 <stdlib.h>
    
    2void _exit(int status)
     _exit()**系统调用函数**
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    • exit()、return()的区别

    exit()是一个库函数, return()是C语言的语句
    exit() 函数最终会进入到内核,把控制权交给为内核,最终由内核去终止进程。
    return并不会进入到内核,它只是一个main函数返回,返回到它的上层调用把控制权交给他的上层调用,最终由上层调用终止进程。

  • 相关阅读:
    ES6包管理机制以及模块化
    解决新版 Kali Linux 在 VMware 虚拟机中设置共享文件夹后依旧寻找不到的问题
    [学习记录] SpringBoot 3. 自动配置
    缺氧诱导因子 HIF在细胞代谢中的作用
    STM32学习历程(day6)
    CentOS7和CentOS8 Asterisk 20.0.0 简单图形化界面8--PJSIP的环境NAT设置
    mysql和sql server 中如何创建和管理用户
    SQL注入之宽字节注入、堆叠注入、二次注入
    LeetBook 刷题笔记:链表(四)
    MySQL---MySQL的安装以及SQL的分类
  • 原文地址:https://blog.csdn.net/StudyPower_Max/article/details/117534902