C语言指针篇

一.指针的本质

        指针的本质就是有类型地址。地址指的是地址总线上的地址码表,而类型决定了我们操作这个指针时,要从这个地址码上读写的长度。即指针的类型决定读写一个地址块的数据长度(1字节、2字节、4字节还是结构体定义的长度)。

        比如(uint8_t *)(0x12345678):C语言中*可以把他后面的内容变成地址码,所以(*)0x12345678)就是把这个常数转化为一个地址码(即指针),那这个指针的类型是什么呢?是uint8_t类型的。那么我们去操作这个指针的时候,就是读写0x12345678这个地址上一字节的内容。

写示例:
*((uint8_t *)(0x12345678))= 0x55;

二.指针的大小、指针变量的值、指向指针的指针以及指针的自增

1.指针的占用的字节数就是单片机地址总线的字节数(位数/8);

2.指针p本身是个变量,只不过这个变量的值是他所指向的内存的地址,通过*p就可以读写那块内  存,读写的数据长度就是指针类型的大小

3.

    如上图所示,q就是个指向指针的指针,q的值就是指针p的地址。通过*q就可以直接读写p的值,其实此时*q就是p。而相对的,p是一个指针,他的值就是buf[8]这个数组第一个元素的地址,通过*p就可以读写buf[0]。我们前面说过,指针是有类型的地址,而当前类型是int8_t,那么让p+1,地址就只会往后走一个int8_t的长度,那么p+1的数值就是buf[1]的地址,*(p+1)就是操作buf[1]了。

    再如果指针p的类型是int,即int *p,如果p等于buf[0]的地址,那p+1以后的值就是往后int长度(4个),p+1的值就是&buf[4],*(p+1)访问的就是buf[4]。

总结

三.数组和指针

1.不要把数组a[n]后面的方括号当成是数组的专利

[n]的真正含义是以前面的指针(有类型的地址)为基准,向后移动长度(n*类型大小)个字节后的地址位置,取出这个位置上的值。

比如:

int a[10];

int *p;

p = a;

此时就可以直接操作p[5],p[5]就是a[5]。

四.void * 没有类型的指针

有的人把void *称作跳跃力不确定的指针,因为void *p,对p++不知道指针会指向哪里。那么void *有什么用呢?

void *包容性是很强的,我们可以把任意类型的地址赋值给他而不会有任何警告,当函数不知道传进来的参数是什么类型时,就可以使用void *

比如uart_receive_data(void *p)这个函数,他不知道传进来的是什么类型,可以先接收。然后在函数内部,用某种类型的指针强制转化以后

接收这个地址,然后再以这种类型解析,如下所示:

uart_receive_data(void *p)
{
struct xx*q;
q=(struct xx*)p;//强制转化为特定类型的指针
//后面再以struct xx的格式对数据进行解析
}

需要注意的是,void *类型的指针赋值给别的指针的时候,必须做强制转化

五.指针的初值及如何区分指针数组和数组指针

1.指针的初值

任何变量初始化的时候都会赋一个初值,如果没有指定初值,会被赋值为0(一般启动文件里面完成)。而为了避免野指针产生问题,C语言有一个保护措施,

那就是如果指针的值为0或者NULL,对这个指针操作都不会对程序产生实质的破坏

2.指针数组和数组指针的区别

可以这样,按类型(后两个字)用括号先结合

比如指针数组:首先是一个数组,那就应该是(p[5]),然后对于指针加上星号*变成*(p[5]),最后加上类型 int变成int *(p[5])

比如数组指针:首先是指针,那就是(*p),然后对于数组加上[ ]就是(*p)[10],最后加上类型就是int (*p)[10]

比如函数指针:首先是指针,那就是(*p),然后是函数,那结构就是type_t (*p)(类型1,类型1),然后再加上类型就是int (*p)(int,int)

3.补充:指向二维数组的指针的定义方法

uint32_t array[2][3]={{1,2,3},{3,4,5}};
uint32_t (*(*p))[3];//定义一个指向二维数组的指针
p=array;
printf("%d",p[0][0]);//访问内容用p[x][y]

六.结构体指针

1.结构体的基本使用

访问结构体变量的内容用'.' ,访问结构体指针里的内容用‘->’

struct student
{
int stuid;
char name[10];
int age;
int score;
}stu1;


struct student stu;
struct student *pstu;
pstu=&stu;
pstu->age = 20;//对于结构体指针,要访问里面的内容就用->
printf("%d",stu。age);//对于结构体变量,访问里面的内容就用.

再看一个结构体嵌套结构体的例子

struct stru1
{
int x;
};
struct stru2
{
    struct stru1 *a;
    int b;
};
int main(void) {
    int i = 0;
    struct stru1 stu1;
    struct stru2 stu2;
    struct stru1 *stu3 = &stu1;
    
    stu2.a = &stu1;
    stu2.a->x = 20;//struct stru2里包含的是stru1 *,这时是用箭头访问stru1内部的内容
    printf("Result is %d",stu2.a->x);//打印结果
    return 0;
}

七.联合体指针

下面举了个简单的例子,功能是实现浮点数取反

typedef union 
{ 
    float a; 
    uint8_t bytes[4]; 
} data_uni;
data_uni x = {.a = 1.0}; 
data_uni *p = &x;
p->bytes[3] ^= 0X80;//这里实现了浮点数的取反
printf("%.f", p->a);

八.malloc的使用

1.不要滥用malloc,malloc的5大使用原则

    1)代码里尽量不用动态分配,尽量使用静态分配,也就是类似于int array[100]这样的方式。将内存在编译期间就定下来。

    2)使用了malloc一定要记得对其所申请的内存进行free。

    3)调用malloc之后,一定要对其返回值进行判断,而不能贸然去使用。因为可能返回个NULL

    4)不要频繁调用malloc,因为malloc会产生内存的碎片,为什么会有碎片,振南后面会详细讲。

    5)适当设置你的heap size,也就是堆的大小

2.malloc背后的原理

    malloc分配的其实是堆里面的空间,这个空间在编译器里面可以设置。malloc就是从堆里面获取一块空的地址块,然后返回这个地址。

堆空间里剩余可用的内存会以链表的形式连接起来。

3.频繁使用malloc和free会导致内存都是碎片

    当我们申请内存时,比如其中一块大小为100,然后我们申请了90,那这块内存就剩下10.下次再申请的时候,由于这块内存太小,大概率不够分配,然后就变成一块碎片内存。如果堆空间里面的大内存变成一块块小内存,虽然空余空间还有,但是malloc申请不到了。

九.关于内存溢出

上图是启动文件中的栈和堆分配的代码

1.预防内存溢出的方法(堆)

    (1)写程序的时候,尽量杜绝malloc之后没有Free的情况,最大限度的保证内存分配的闭环。一个原则是:谁申请谁释放。

    (2)如果是基于嵌入式操作系统,比如freeRTOS、rt thread的话,可以去看一下程序运行过程中的内存是否有持续的增长,如果呈现不断发散的趋势,那很有可能你的程序中有隐藏的内存泄露或者malloc后没free。

2.预防栈溢出

1、函数里局部变量的总大小不能太大;

2、函数的嵌套调用层数不要太多;

3、函数的规模不宜太大,一般都是建议50~200行;

十.STM32的启动流程

首先把0x08000000地址上的内容赋值给MSP(栈指针),然后取出0x08000004地址复位函数的地址并赋值给PC指针,这样程序就跳转到Reset_Handler了,在Reset_Handler中有一个SystemInit(变量等的初始化)以及_main,最终程序进入到main

十一.STM32的bootloader核心代码

这里不做说明
typedef void (*iapfun)(void);
iapfun jump2app;

void MSR_MSP(uint32_t addr)
{
    //set main stack value
    _ASM volatile("MSR,MSP,r0");
    _ASM volatile("BX r14");
}
void iap_load_app(uint32_t appaddr)
{
__disable_irq();
jump2app=(iapfun)(*vu32)(appaddr+4);
MSR_MSP(*vu32)(appaddr);
//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app();//跳转到APP.
}

十二.柔性数组

方法1:
// 定义数据帧结构体,包括头部信息、数据类型以及一个指向缓冲区的指针
typedef struct 
{
uint8_t header; // 数据帧头部
uint16_t data_type; // 数据帧类型
uint8_t *pbuf; // 缓冲区指针
} data_frame;


// 动态分配一个data_frame类型的内存空间
data_frame *p = (data_frame *)malloc(sizeof(data_frame));


// 再次动态分配一个datalen大小的内存空间,并将其赋值给p->buf
p->pbuf = (uint8_t *)malloc(datalen);


// 从串口1接收datalen长度的数据,并存入p->buf中
uart1_receive_data(p->buf, datalen);
//注意及时free,避免代码泄露

结构体中的pbuf只是一个指针,通过指向malloc申请的一块内存空间,使得通过p->buf可以访问一个按照需要分配的内存空间。但是这样也可能会有些问题,因为我们申请动态内存后,一般只会free(p),不会free(p->buf),所以会导致这一块内存变成僵尸内存。

方法2:
typedef struct
{
    unsigned char head;//1字节
    short data_type;   //2字节,由于结构体的对齐规格和前面之间空着1字节
    unsigned char pbuf[];//占位,但是空间不在
}data_Frame;
int main()
{
    data_Frame *p = (data_Frame *)malloc(sizeof(data_Frame)+10);
   /*  Write C code in this online editor and run it. */
    printf("p->pbuf=%d,p+1=%d\n",p->pbuf,p+1);//值是一样的,说明pbuf占的位置是在下个结构体开始的位置
    printf("Frame's size=%d\n",sizeof(data_Frame));//大小是4,说明pbuf占位的空间不算在结构体中
   return 0;
}

通过pbuf[]占位,malloc申请的内存空间大一点,由于pbuf[]所在的地址就在p的后面,所以p->buf和p+1指向的位置是一样的。这样就住需要malloc一次,释放也只要free(p)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小吴的嵌入式笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值