《C语言内核深度解析》——笔记及拓展(1)

文章是我前几天读了朱有鹏,张先凤老师的《嵌入式Linux与物联网软件开发:C语言内核深度解析》写的,拜读之后,虽没有醍醐灌顶,至少解开了我之前的一些疑惑。

《嵌入式Linux与物联网软件开发:C语言内核深度解析》目录

以前学C语言,就是在IDE上编一下代码,编译器会有错误有警告提示,很少思考过变量、指针、结构体、函数之间,及所编代码和当前所运行系统的关系(系统内存有多大,运行速度怎样,怎样优化算法)。等我真正学了嵌入式,才开始思考上面的问题,才开始去了解软件与硬件的关系。

本文章是边复习边记笔记,跟书籍目录不一样,以后可能会补充及修改。

有纰漏请指出,转载请说明。

学习交流请发邮件 1280253714@qq.com

第一章 C语言与内存

  • 程序=数据+算法

  • 内存

作用:存储可变数据(全局、局部变量)。

变量就是在内存中分配一块内存空间,将它的地址和变量名相关联。

/*定义一个变量a,以0x0061ff1c开始的四个字节的地址存放a的值*/
int a=0;
printf("size of a:%d\n",sizeof(a));   //4
printf("address of a:%p\n",&a);       //0061ff1c

分类:SRAM、DRAM(SDRAM、DDR1、DDR2...LPDDR)

  • 数据和代码存放方式

冯.诺依曼:数据和代码放一起

哈佛结构:数据和代码分开存储

  • 数据结构

数据结构研究数据如何组织并如何在内存中存放

  • 如何管理内存

  1. 汇编:操作内存时直接使用内存地址

  1. C语言:编译器帮我们管理内存,我们通过编译器提供的变量名来访问内存。需要用到大块内存时,有OS时通过API(malloc free)来访问系统内存;无OS时自己定义数组、结构体等来解决

  1. c++:new对象(为对象分配内存),使用完delete对象(释放内存)。如果使用完忘记delete,对象占用的内存就无法释放,也就是内存泄露

  1. Java、C#、python等:通过虚拟机来操作内存

  • 内存本质

从逻辑角度看,内存是很多个内存单元格组成,单元格存放数据,每个单元格有一个固定的地址

通俗讲,内存(主存、显存等)可看做一栋大楼,大楼里每个房间都有门牌号,房间里住着的人就是数据,我们要去访问某个人,要知道他住哪一层哪一个房间

同理CPU操作内存过程大致如下

读写控制总线控制对内存进行读或写的操作

通过地址总线访问内存

通过数据总线传输数据

  • bit、byte、word等

bit:0、1

byte:8bit

word:一般说32位系统,一个word对应32bit。不同的系统占据的空间不同,不需要死记,C语言用sizeof即可,用法如下:

#include <stdio.h>
#define N 5

void main()
{

    int array[N]={1,2,3,4,5};

    printf("\t  sizeof(char)=%d                        \n",sizeof(char));
    printf("\t  sizeof(int)=%d                         \n",sizeof(int));
    printf("\t  sizeof(short)=%d                       \n",sizeof(short));
    printf("\t  sizeof(long)=%d                        \n",sizeof(long));
    printf("\t  sizeof(unsigned int)=%d                \n",sizeof(unsigned int));
    printf("\t  sizeof(unsigned short)=%d              \n",sizeof(unsigned short));
    printf("\t  sizeof(unsigned long)=%d               \n",sizeof(unsigned long));
    printf("\t  sizeof(float)=%d                       \n",sizeof(float));
    printf("\t  sizeof(double)=%d                      \n",sizeof(double));
    printf("\t  sizeof(array)=%d                       \n",sizeof(array));
    printf("\t  sizeof(array)/N=%d                     \n",sizeof(array)/N);
    printf("\t  sizeof(array)/sizeof(array[0])=%d      \n",sizeof(array)/sizeof(array[0]));

}
/*         
sizeof(char)=1
sizeof(int)=4
sizeof(short)=2
sizeof(long)=4
sizeof(unsigned int)=4
sizeof(unsigned short)=2
sizeof(unsigned long)=4
sizeof(float)=4
sizeof(double)=8
sizeof(array)=20
sizeof(array)/N=4
sizeof(array)/sizeof(array[0])=5
*/
  • 函数名

C语言中,函数是一段代码的封装,函数名(指针)是这一段代码的首地址,有了函数名,就有了地址,才实现函数的调用

#include <stdio.h>
int add(int a,int b){
    return a+b;
}
int main(void){
    printf("%p\n",add);    //00401400
    printf("%p\n",*add);    //00401400
    printf("%p\n",&add);    //00401400
    return 0;
}
  • 数组

数组和变量没有本质区别,只是解析方法不同。

数组就是在内存中开辟一块连续的内存空间,数组名就是内存空间的首地址

用指针来访问元素,每移动一个元素位置,实际上移动一个元素空间的大小(如下,(arr+1)比arr的地址多了4个字节)

int main(void){
    int arr[5]={1,2,3,4,5};
    
    printf("%d\n",sizeof(arr));                //20
    printf("%d\n",sizeof(*arr));               //4
    printf("%d\n",sizeof(arr)/sizeof(*arr));   //5

    printf("%p\n",arr);        //0061ff0c
    printf("%p\n",&arr);       //0061ff0c
    printf("%p\n",&arr[0]);    //0061ff0c
    printf("%p\n",&arr[1]);    //0061ff10
    printf("%p\n",arr+1);      //0061ff10
    printf("%d\n",*arr);       //1
    printf("%d\n",arr[0]);     //1
    printf("%d\n",arr[1]);     //2
    printf("%d\n",*(arr+1));   //2
    return 0;
}

数组优点:定义简单,方便访问

数组缺点:

  1. 数组中所有元素类型必须相同

  1. 数组大小必须在定义时给出

  1. 数组的空间必须是连续的(解决办法是使用链表)

  • 结构体

结构体属于聚合数据类型,结构体变量在被定义后,编译器在编译时会为所有成员分配空间

结构体变量名代表的是整个结构体变量,不像数组名代表地址

#include <stdio.h>

typedef struct {
    char id[8];
    char name[8];
    int age;
}Student;

int main(void){
    Student stu={"2019","ckj",18};
    printf("%p\n",&stu);
    printf("%p\n",&stu.id);
    printf("%p\n",&stu.id[0]);
    printf("%p\n",&stu.id[1]);
    printf("%p\n",&stu.age);    
    return 0;
}

向函数传递结构体时,实际上是传递结构体成员的值,但是如果定义的结构体是多成员结构或者有数组的结构时,把整个结构体变量进行压栈,运行性能会恶化。解决办法是向函数传递结构体指针,压栈的仅仅是结构体变量的地址

#include <stdio.h>

typedef struct {
    char id[8];
    char name[8];
    int age;
}Student;

/*
函数addAge1被调用时,在函数栈中开辟stu1的变量空间
可看做复制stu给stu1,实际上操作的是stu1与main函数的stu无关
*/
Student addAge1(Student stu1){
    stu1.age++;
    return stu1;
}

/*
指针stu2指向stu
即在addAge2中直接操作变量stu
*/
void addAge2(Student* stu2){
    (stu2->age)++;    
}
int main(void){
    Student stu={"2019","ckj",18};
    addAge1(stu);
    printf("%d\n",stu.age);            //18
    addAge2(&stu);
    printf("%d\n",stu.age);            //19
    printf("%d\n",addAge1(stu).age);   //20 仅仅把返回值输出而已,实际上stu.age还是19
    printf("%d\n",stu.age);            //19
    return 0;
}
  • 栈(自动管理区)

使用:函数内部的局部变量

定义:栈用来保存局部变量,保存函数调用所需的所有维护信息,栈是一种维护内存的机制。运行时空间自动分配,运行结束空间自动回收。

特点:FILO先进后出,栈指针的移动和内存分配是自动完成的,不需要程序参与。能够被反复使用。

约束:栈有大小,溢出危害很大,所以定义局部变量时不能太多或太大(如不能定义局部变量int a[10000])

临时性:函数不能返回栈变量的指针,这个空间在运行结束就会被释放。

  • 堆(动态管理区)

使用:变量只在程序的一个阶段期间有用

定义:动态内存管理方式

特点:

  1. 容量不限,动态分配

  1. 申请(malloc)和释放(free)需要手工进行,不释放会存在内存泄露

#include <stdio.h>
#include <stdlib.h>

int main(void){
    //一:申请1000个int型元素的数组
    int *p=(int *)malloc(1000*sizeof(int));

    //二:检验申请是否成功
    if(p==NULL){
        printf("malloc error \n");
        return -1;
    }

    //三:使用申请到的内存
    *(p+0)=1;
    *(p+1)=2;

    //四:释放
    free(p);
    return 0;
}
  • 静态空间

编译器在编译程序时就已经确定了静态存储区的大小,程序运行结束才释放内存空间

  1. 代码区:存放指令代码,readOnly,(特殊数据段如 char *p="hello"; 字符串“hello”分配在代码段)

  1. 常量区:存放程序中用到的常量(const),readOnly

  1. 静态数据区:存放静态局部变量和全局变量,rw

第二章 C语言位操作

这一部分还是书里写得比较精彩,我只是总结我需要的part,读者请阅读原书或请跳过

  • 位操作符与逻辑操作符

&按位与,&&逻辑与

|按位或,||逻辑或

~按位取反,!非

  • 位异或^(两次异或可以取得原值)

a = 0 0 0 0 1 0 1 0;

b = 0 0 0 1 0 1 0 0;

第一次异或 a^b= 0 0 0 1 1 1 1 0;

第二次异或a^b^b= 0 0 0 0 1 0 1 0;

即a^b^b == a^(b^b)

因为一个数异或自己等于 0000 0000,而 0000 0000异或任何数字,结果是任何数字本身(两次异或可以取得原值,用在简单加密上)

  • 位操作与寄存器

操作片内外设,就是操作片内外设的控制寄存器。控制硬件就是读写寄存器(寄存器也可理解为特定地址的内存)

对于32位嵌入式系统,寄存器是32位的,无法直接修改寄存器中的某一位,因此,修改寄存器的特定位,需要把32bit的数据整体读出,修改后再写入。即读-改-写

  • 使用移位获得特定位为1的二进制数

bit3~bit7、bit23~bit25为1

int a = 0b 0000 0011 1000 0000 0000 0000 1111 1000; //此处空格应删去
int a = 0x038000f8;
int a = (0x1f<<3) | (0x07<<23);
  • 使用移位获得特定位为0的二进制数

bit4~bit10为0

int a = 0b 1111 1111 1111 1111 1111 1011 1110 1111; //此处空格应删去
int a = 0xfffffbef;
int a = ~(0x7f<<4);
  • 练习1

给定整型数a,清除a的bit15~23,保持其他位不变

a &= ~(0x1ff<<15);
  • 练习2

给定整型数a,取出a的bit3~bit8

a &= (0x3f<<3);
a >> 3;
  • 练习3

给寄存器a的bit7~bit17赋值937

(将bit7~bit17清零,再将937设置到bit7~bit17)

a &= ~(0x7ff<<7);
a |= (937<<7);
  • 练习4

给寄存器a的bit7~bit17中的值加17

(直接 a += 0x00000f80 可能会溢出影响到其他位)

  1. 读出bit7~bit17

  1. bit7~bit17加17

  1. 将bit7~bit17清零

  1. 将第二步算出的值加17

tmp = (a&(0x7ff<<7));
tmp >>= 7;
tmp += 17;
a &= ~(0x7ff<<7);.
a |= tmp<<7;
  • 练习5

直接操作寄存器,点亮stm32的红灯(GPIOB_5输出低电平)

步骤

  1. 开启GPIOB所在的APB2的RCC时钟

  1. 配置GPIOB的CRL寄存器,设置GPIO模式

  1. 配置GPIOB的ODR寄存器,PB5输出低电平

/*define GPIOB_ODR    = *(unsigned int*)(0x40010c0c);*/

//RCC_APB2ENR bit3置1,即GPIOB端口时钟使能
*(unsigned int*)(0x40021018) |= (1<<3);

//清空GPIOx_CRL 的bit20~bit23            
*(unsigned int*)(0x40010c00) &= ~( 0x0F<< (4*5)); 
//向GPIOx_CRL 的bit20~bit23 写入3,即PB5以50MHz通用推挽输出 
*(unsigned int*)(0x40010c00) |= (3<<4*5);          

//GPIOx_ODR 的 bit5 置0,即PB5输出低电平
*(unsigned int*)(0x40010c0c) |= ~(0x20);                

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值