当前位置: 首页 > news >正文

指针深入理解(二)

volatile关键字

防止优化指向内存地址,

typedef

指针可以指向C语言所有资源
typedef 就是起一个外号。

指针运算符加减标签操作

指针加的是地址,并且增加的是该指针类型的一个单位,指针变量的步长可以用sizeof(p[0])
在这里插入图片描述
这两个的p+1是不一样的,因为char是一个字节,int是四个字节,这样char是一个内存地址就可以存储,但是int需要四个内存地址存储。但是默认都是基地址,

指针数组?
在这里插入图片描述
地址的标签访问,一定不要理解成数组
标签是访问,p想象成地址变量,然后我们通过标签进行访问,并且这个标签(0、1、2、3)是C语言内部给我们处理好的。
P[2] 相当于是取出内存的值,P+2:指的是内存地址,

变量分配是从内存地址的高到低分配的

int a = 0x12345678;
int b = 0x99999999;
内存是先分配a,然后是b,并且a的地址是高字节,b的是地址是低字节。
取地址,一定是取最低的字节的地址,

内存是先分配a,然后是b,并且a的地址是高字节,b的是地址是低字节。
取地址,一定是取最低的字节的地址,

    volatile int a = 0x12345678;    int b = 0x99991199;    int c = 1;c = a;int *p1 = &b;char *p2 =(char *)&b;printf("the p1+1 is %x,%x,%x\n", (p1+1), p1[1],*p1+1 );printf("the p2+1 is %x,%x,%x\n", p2[1],p2,c);printf("the p2+1 is %x,%x,%x\n", p1,p1[0],p1+1);    printf("the p2+1 is %x,%x,%x,%x\n", p2,*p2,p2+1,p2[1]);      printf("the p2+1 is %x,%x,%x,%x\n", p1,*p1,p1+1,p1[1]);the p1+1 is 403ffa44,12345678,9999119athe p2+1 is 11,403ffa40,12345678the p2+1 is 403ffa40,99991199,403ffa44the p2+1 is 403ffa40,ffffff99,403ffa41,11the p2+1 is 403ffa40,99991199,403ffa44,12345678

可以明显看出,在分配内存的时候,是从高字节到低字节,
在取内存的时候是从低字节到高字节
并且在p2[1],和p1[1],有明显的区别。
首先p2和p1都是指向的变量b的基地址都是一样的,都是地址6bbffcb0
但是由于指针的类型不一样,导致我们在将基地址加1的时候或者说我们在按照地址的标签访问的时候,p1[1]的步长是四个字节,也就是指向了变量a,因此p[1]指向的值是0x12345678。相反我们将p2的指针类型进行了强制转换为char,导致步长变为1,那么p2[1]指向的值是0x11。从这里我们得出结论,改变指针的类型,并不会改变存储该变量的地址,只是会改变指针标签访问对象,或者说是以基地址位基准的步长会影响,也就是可能会导致我们取地址存储的值的时候取不出来完整性。[引起数据截断]
如这篇文章的解释
指针深入理解(一)-CSDN博客

强制类型转换不改变指针存储的地址值,仅改变对内存数据的解释方式
也就是我原本这四个地址存储的是一个数0x99991199
我使用的是int类型表示的,那么我使用* p2的时候,默认他能看到全部的这四个内存地址,那么就会一口气读出来,告诉我这个值是0x99991199。
但是我强制转换以后,我相当于是眼睛被限制了,我只能看到基地址(也只仅仅是基地址),步长就是1,眼界也就是如此,被限制了。
指针的类型,会限制他看到的内存地址的宽度,(或者是理解成一种类似于宽度的概念。)

所以说定义指针的时候必须要定义是什么类型的,不然都没有眼睛了,怎么取地址,怎么看,另一个就是地址在哪里,不然有了眼睛不知道看到哪里了,不是一样的没有意义。也就是俗称野指针。

    volatile int a = 0x12345678;    int b = 0x99991199;    int c = 1;c = a;int *p1 = &b;char *p2 =(char *)&b;printf("the p1+1 is %x,%x,%x\n", (p1+1), p1[10],*p1+1 );the p1+1 is 1ff754,73ef12ee,9999119a   

这是指针的越界,从语法角度越界是允许的,粗暴一点就理解为修改游戏的一些参数,开外挂。
但是在产品中,不能越界,因为会出现一些未知的Bug,导致出现严重Bug。造成不可估量的损失。内存泄漏

有些内存段是不能读的,强行访问就会出现段错误。

越界访问:

    volatile int c = 0x11223346;volatile int a = 0x12345678;  int b = 0x11223344;// a = 100;// printf("the p2+1 is %x,%x,%x\n", a,p2,c);int *p = &b;// int *p1 = &a;      // p[1] = 0x100;printf(" %x,%x\n", p,*p);printf(" %x\n", *(p) );    printf(" %x\n", p[1]);  printf(" %x\n", p[2]);  printf(" %x\n", p[3]);  printf(" %x\n", p[4]);ca7ff73c,11223344112233441234567811223346ca7ff73c8int c = 0x11223346;int a = 0x12345678;  int b = 0x11223344;// a = 100;// printf("the p2+1 is %x,%x,%x\n", a,p2,c);int *p = &b;// int *p1 = &a;      // p[1] = 0x100;printf(" %x,%x\n", p,*p);printf(" %x\n", *(p) ); printf(" %x\n", p[1]);  printf(" %x\n", p[2]);  printf(" %x\n", p[3]);  printf(" %x\n", p[4]);b47ffc8c,1122334411223344b47ffc8ce71234567811223346

可以看出这两个部分增加Volatile是有区别的,这是跟内存和编译器是有很大区别。

并且const并不是不能改变,只是一种建议字符,可以通过越界的方式访问

    int c = 0x11223346;const int a = 0x12345678;  int b = 0x11223344;// a = 100;// printf("the p2+1 is %x,%x,%x\n", a,p2,c);int *p = &b;// int *p1 = &a;      p[3] = 0x100;printf(" %x,%x\n", p,*p);printf(" %x\n", *(p) );    printf(" %x\n", p[1]);  printf(" %x\n", p[2]);  printf(" %x\n", p[3]);  printf(" %x\n", p[4]);1c5ffe0c,11223344112233441c5ffe0c8210011223346   

可以看出const修饰的a还是被改变了。
所以一定不能越界,不然就会出现未知错误。

但是这里有一些疑问,例如为什么内存地址没有连续,以及增加了volatile以后就会不一样,这可能需要以后了解了编译器原理才能对此进行深入理解,暂时就先不纠结了。

指针逻辑操作符

<= >= == != &&

一般是用的是跟一个特殊值进行比较,== 或者 != 0x0. 0值进行比较 地址的无效值,结束标志,没有空间。
NULL 这个就是一个0,只不过是一个任意类型的0。

指针是同类型比较,才能编译通过。不能说char和int进行比较。

多级指针(二维指针)

int * *P

说白了就是存放的是地址,只不过这个地址指向的是一个值。
说穿了就是一级一级的套娃,
只是将一级指针的地址给存储起来了。

char ** p
二维指针。
把一些毫不相干的东西,组合成一种线性关系。描述的不是内存是什么,而是内存与内存之间的关系。
感性理解是
在这里插入图片描述
原本这两个字符的内存是不连续的,可以说是毫无关系,但是我通过多级指针,可以将他们的内存联系起来,构造到一个连续的内存空间,“相当于是将原本存储字符串的内存地址,在外面给包裹起来了,然后让他们的地址存出来连续的内存中,多了一层封装,从而形成了一段线性关系。”
二维指针的大小,
p[m]== NULL的时候,相当于这个内存空间结束了。二维指针结束标志。
在这里插入图片描述一定要记住这个模型,
地址的结束标志是NULL,

在这里插入图片描述

static关键字

static uint8_t bvalid = 0;static uint8_t bvalid; bvalid = 0;` 这两种写法在语法和底层逻辑上存在关键区别,能否等价替换取决于代码所处的作用域和实际需求。以下是具体分析:


1. ​全局作用域或文件作用域

  • 原写法 static uint8_t bvalid = 0;
    这是标准的静态变量初始化方式,编译器会在程序启动时自动完成初始化,且
    仅执行一次
    。静态变量的默认初始化值为0,但显式赋初值(如=0)可以提高代码可读性
  • 改写为 static uint8_t bvalid; bvalid = 0;
    语法错误
    ​:在全局作用域中,bvalid = 0; 是赋值语句,无法直接出现在函数体外。C语言规定全局作用域只能进行变量声明和初始化,不能包含可执行代码

2. ​函数内部(局部静态变量)​

  • 原写法 static uint8_t bvalid = 0;
    这是局部静态变量的初始化方式,​
    仅在首次进入函数时执行一次
    ,后续调用时保留修改后的值

    void func() {static uint8_t bvalid = 0;  // 仅初始化一次bvalid++;printf("%d", bvalid);  // 输出会递增:1, 2, 3...
    }
    
  • 改写为 static uint8_t bvalid; bvalid = 0;
    虽然语法合法,但
    语义不同
    ​:bvalid = 0; 会在每次调用函数时执行,导致静态变量被重复重置为0。例如:

    void func() {static uint8_t bvalid;      // 默认初始化为0(仅一次)bvalid = 0;                // 每次调用时赋值bvalid++;printf("%d", bvalid);      // 始终输出1
    }
    

    这破坏了静态变量的“持久性”特性


3. ​总结与建议

场景原写法 static uint8_t bvalid = 0;改写 static uint8_t bvalid; bvalid = 0;
全局/文件作用域正确,初始化仅一次语法错误,无法编译通过
函数内部正确,初始化仅一次,保留持久性合法但语义错误,每次调用都会重置变量
  • 推荐做法​:
    若需静态变量仅初始化一次并保留其持久性,必须使用原写法。
    若需每次进入作用域时重置值​(如函数内需重复初始化的场景),应避免使用静态变量,改用普通局部变量。

4. ​底层机制

  • 初始化 (=0)​​:
    在编译或首次运行时完成,存储在程序的.data.bss段(未显式初始化的静态变量默认置0)

  • 赋值 (bvalid = 0)​​:
    是运行时操作,每次执行时覆盖当前值,可能产生额外的机器指令


5. ​特殊情况

若静态变量是复杂类型(如结构体或类对象),初始化和赋值可能涉及构造函数与赋值运算符的调用差异,但对基本类型(如uint8_t)两者效果相同(仅语义不同)


结论​:在用户提供的代码片段中,若 bvalid 需保持静态变量的持久性,不能直接改写为分开的声明和赋值形式。若在函数内部强制要求每次调用时重置值,需权衡是否真正需要静态存储特性。

【版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。】

http://www.xdnf.cn/news/558181.html

相关文章:

  • 在表格中使用AI解析通信协议
  • Vue3 父子组件传值, 跨组件传值,传函数
  • 进程——概念及状态
  • 算法训练之分治(快速排序)
  • 浏览器播放 WebRTC 视频流
  • 从客厅到驾驶舱:FSHD 如何成为全场景显示「破局者」
  • 第四十一节:人脸检测与识别-Haar 级联分类器
  • 城市共治的伦理平台愿景
  • 第6天-Python操控摄像头:从入门到实战
  • 四元数中 w xyz 的含义及应用
  • 通义灵码助力JavaScript开发:快速获取API与智能编码技巧
  • celery独立部署接入数据库配置
  • 【C++算法】68.栈_字符串解码
  • 关于Linux服务器数字取证一
  • pytorch小记(二十四):PyTorch 中的 `torch.full` 全面指南
  • Python 包管理工具 uv
  • RocketMQ 的事务消息是如何实现的
  • 【Java高阶面经:微服务篇】3.熔断机制深度优化:从抖动治理到微服务高可用架构实战
  • unipp === 状态管理 Pinia 使用
  • 萌新联赛第(三)场
  • 自建主机NAS
  • Java转Go日记(四十二):错误处理
  • 链表-设计链表
  • OBS Studio:windows免费开源的直播与录屏软件
  • Tractor S--二维转一维,然后最小生成树
  • Python 中 pass 语句的详解和使用
  • Java双指针法:原地移除数组元素
  • IEEE出版|2025年智能光子学与应用技术国际学术会议(IPAT2025)
  • CRC计算
  • doris数据分片逻辑