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

1. C++ 中的 C

1. 认识 volatile

1.1 volatile 的作用

每次都从内存中读取该值,而不会因编译器优化从缓存的地方读取(如寄存器中,由于访问寄存器的速度比 RAM 快),从而保证 volatile 变量被正确读取。

  • 问题示例:从寄存器中读取值,被修改的变量值不能及时得到反应。
#include <iostream>
using namespace std;int main() {int i = 10;int a = i;cout << a << endl;_asm {mov dword ptr[ebp - 4], 80}int b = i;cout << b << endl;return 0;
}

VS 2022 环境下生成 x86 Release 版本(Release 模式下才会对程序代码进行优化),输出结果如下,可见 i 的值还是 10,并没有变成 80。
在这里插入图片描述

  • 解决方法:volatile

VS 2022 环境下生成 x86 Release 版本,用 volatile 定义变量 i, 输出结果如下。
在这里插入图片描述

1.2 volatile 的应用场景

1.2.1 并行设备的硬件寄存器

假如要初始化一个设备,这个设备的某个寄存器为 0xff800000:

int* output = (unsigned int*)0xff800000;int init() {for (int i = 0; i < 10; i++) {*output = i;}
}

编译器经过优化后认为前面的循环是无用的,对最后的结果毫无影响,因为最终是将 output 这个指针的内容赋值为 9,所以最后编译器编译的代码结果相当于:

int init() {*output = 9;
}

但如果对此设备进行初始化的过程必须像开头一样顺序地对其赋值,则优化过程显然不行。需要加 volatile。

1.2.2 一个中断服务子程序中会访问到的变量

static int i = 0;int main() {while (1) {if (i) {dosomething();}}
}// 中断服务程序
void IRS() {i = 1;
}

代码本意是产生中断时,IRS 响应,i 被置为 1,然后 main 中 dosomething()。但是,编译器判断在 main 中没有动过 i,因此可能只执行一次把 i 读到寄存器的操作,然后 if 判断时都只用这个寄存器里的值,导致 dosomething 永远无法执行。

1.2.3 多线程应用中被几个任务共享的变量

volatile bool bStop = false;void threadFunc1() {while (!bStop) {......}
}void theadFunc2() {bStop = true;
}

如果没有 volatile 修饰,threadFunc1 中将是个死循环,因为 bStop 已经被读到寄存器中。

1.3 volatile 的常见问题

1.3.1 一个参数既可以是 const 也可以是 volatile 吗?为什么?

YES!例如只读的状态寄存器。

1.3.2 一个指针可以是 volatile 吗?为什么?

YES!例如:这里需要注意两点:

  1. volatile int *p; p 是指向 volatile int 的普通指针,*p = 10; 每次访问都会真正读写内存,因为 *p 的内容可能随时被硬件或其他线程修改,所以不能优化。
  2. int * volatile p; p 是一个 volatile 指针,指针变量本身可能随时被外部改变(例如通过 DMA、硬件寄存器映射)
  3. 还有 volatile int * volatile p;

1.3.3 下面的函数有什么错误?

int square(volatile int* ptr) {return *ptr * *ptr;
}

因为加了 volatile,所以上述代码变成下面:

int square(volatile int* ptr) {int a, b;a = *ptr;b = *ptr;return a * b;
}

由于 *ptr 可能被改变,所以 a 和 b 可能不相同,导致代码不符合预期。可以修改为如下:

int square(volatile int* ptr) {int a;a = *ptrreturn a * a;
}

2. 数组与指针详解

2.1 数组

2.1.1 数组名

数组名,本质是一个文字常量(指针常量),代表第一个元素的地址和数组的首地址。数组名本身不可寻址(理解为不能类似自增这种操作,改变其值,也呼应了这句开头的文字常量)。

int a[10];int* &r = a; // 错误
int* const &r = a; // 正确

2.1.2 数组类型

  • 对于一个整型变量,其类型是 int。那么对于数组 a,它的类型是怎样的?其类型有两部分,一个是元素类型,一个是元素个数。所以对于 sizeof 来说,能够计算出来占用的空间大小就是数组类型的大小,也即 元素个数 乘上 元素类型的大小。
  • 对于数组 a 来说,a、&a[0]、a+0 是等价的。

2.1.3 数组的引用

  • 对于数组 a,在数据区开辟一个无名临时变量,将数组 a 的地址常量复制到该变量中,再将常引用 r 与此变量进行绑定。即先有 int *,表示复制数组 a 的地址,然后是一个常引用 const &。
  • 另一种就比较直观了,直接数组类型 int [4],然后用一个引用 &。
int a[4];int* const &r = a;
int (&ra)[4] = a;

2.1.4 数组指针

数组指针,数组 a 和 &a 的类型是不一样的,a 的类型就是数组类型 int[4],在表达式中退化为 int*;而 &a 是取数组的地址,也即它的类型是 int(*)[4],所以 a+1 表示第 2 个元素的地址,而 &a + 1 是跳过一个数组大小的地址。示例如下:

#include <iostream>
using namespace std;int main()
{int a[4] = {};int* const &r = a;int (&ra)[4] = a;cout << a << endl;cout << &a << endl;cout << a+1 << endl;cout << &a+1 << endl;return 0;
}
$ g++ test.cpp
$ ./a.out
0x7ffe02deeeb0
0x7ffe02deeeb0
0x7ffe02deeeb4
0x7ffe02deeec0

2.2 指针

任何指针类型所占用的空间一般都是 4 字节(在 32 位平台上)。比如 sizeof(int*),sizeof(float*),等等。
野指针:指向非法内存地址的指针

2.2.1 使用未初始化的指针

int *p;
cout << *p << endl;

2.2.2 指针所指向的对象已经消亡

#include <iostream>
using namespace std;int* retAddr() {int num = 10;   // 局部变量,存放在栈上return &num;    // ❌ 返回局部变量的地址
}int main() {int* p = NULL;p = retAddr();          // p 接收了一个已经无效的地址cout << &p << endl;     // 打印 p 这个指针变量自己的地址cout << *p << endl;     // ❌ 解引用一个悬空指针,未定义行为
}

2.2.3 指针释放之后未置空

#include <iostream>
using namespace std;int main() {int* p = NULL;p = new int[10];     // 在堆上分配一个大小为 10 的 int 数组delete p;            // ❌ 错误释放方式cout << "p[0]:" << p[0] << endl;  // ❌ 使用已释放的内存
}

2.2.4 realloc() 函数使用不当

#include <malloc.h>void main() {char *p, *q;p = (char *)malloc(10);q = p;p = (char *)realloc(p,20);// ...
}
  1. p = malloc(10)
    向堆申请 10 字节内存,返回首地址赋给 p。此时 q = p,所以 qp 指向同一块 10 字节内存。
  2. p = realloc(p, 20)
    试图把 p 指向的内存扩展到 20 字节。
    如果原地址后面刚好有足够空闲空间realloc 会直接在原地扩展,返回的地址和 p 相同,q 依然有效。
    如果原地址后面没有足够空间realloc 会:
    • 在堆中找一块新的 20 字节内存(相当于 malloc(20)),
    • 把原来 10 字节的数据拷贝到新内存,
    • 释放原来的 10 字节内存,
    • 返回新内存的地址。
    • 此时 p 被更新为新地址,但 q 依旧保存着旧地址——而旧内存已经被释放。
  3. 所以 q 成为了 悬空指针(dangling pointer)
    如果再用 q 访问数据,就会导致 未定义行为,可能导致崩溃或数据错误。
  • 正确的写法:如果真的要保留旧数据的引用,就要明确判断 realloc 的返回值,并避免让 q 成为野指针。例如:
char *p, *q;
p = malloc(10);
q = p;char *tmp = realloc(p, 20);
if (tmp != NULL) {p = tmp;// q 现在不能再安全使用旧地址q = p;   // 如果还要 q 继续指向新地址,就必须更新
} else {// realloc 失败,p 仍然指向原来的 10 字节
}

3. 文字常量和常变量

3.1 文字常量

文字常量:编译后写在代码区,不可寻址不可更改。包括 数值常量,字符常量和符号常量。

int &r = 5; // ❌ 错误

因为数值常量没办法寻址。但下面的却是可以的,编译器将数值常量转变为了常变量,在数据区开辟了一个值为 5 的无名整型常变量,然后引用绑定。

const int &r = 5;

3.2 常变量

常变量:定义时必须显式初始化且值不可修改的变量。可以寻址。例如 字符串常量。
全局常变量存储在只读存储区,不可修改;局部常变量存储在栈上,可以间接修改。


文章转载自:

http://Sonk4vWn.sxwfx.cn
http://o5FGtePR.sxwfx.cn
http://CiPlrWya.sxwfx.cn
http://fX7Pi1hx.sxwfx.cn
http://S7EWKZXS.sxwfx.cn
http://ycP6WTgL.sxwfx.cn
http://i9jnKhMF.sxwfx.cn
http://oqXeVYo8.sxwfx.cn
http://KPTfxDKQ.sxwfx.cn
http://IKqoPAwJ.sxwfx.cn
http://snsPXxOl.sxwfx.cn
http://MCjCVSPK.sxwfx.cn
http://S88h4b5V.sxwfx.cn
http://xYEI3zma.sxwfx.cn
http://Cc3d8P3v.sxwfx.cn
http://cyYdx62c.sxwfx.cn
http://VeGTrNXi.sxwfx.cn
http://J3vxoRzF.sxwfx.cn
http://fN5pAELO.sxwfx.cn
http://S1Lv55OD.sxwfx.cn
http://MmJfbkRE.sxwfx.cn
http://qIHOVVek.sxwfx.cn
http://WBSSDOhs.sxwfx.cn
http://zWk6zOk0.sxwfx.cn
http://X2cRD2Tb.sxwfx.cn
http://GrK8UMnI.sxwfx.cn
http://XPAp7dQ6.sxwfx.cn
http://h8e2HvcL.sxwfx.cn
http://QV1CFVkm.sxwfx.cn
http://3I6sdPKl.sxwfx.cn
http://www.dtcms.com/a/386819.html

相关文章:

  • 探讨基于国产化架构的非结构化数据管理平台建设路径与实践
  • C++11移动语义
  • 代码随想录第14天| 翻转、对称与深度
  • 算法改进篇 | 改进 YOLOv12 的水面垃圾检测方法
  • 一个我自己研发的支持k-th路径查询的数据结构-owl tree
  • 首款“MODA”游戏《秘境战盟》将在Steam 新品节中开放公开试玩
  • ε-δ语言(Epsilon–Delta 语言)
  • QCA9882 Module with IPQ4019 Mainboard High-Performance Mesh Solution
  • xv6实验:Ubuntu2004 WSL2实验环境配置(包括git clone网络问题解决方法)
  • ICE-Interactive Connectivity Establishment-交互式连接建立
  • 【代码随想录day 28】 力扣 45.跳跃游戏 II
  • IP核的底层封装
  • 4.PFC原理和双闭环控制
  • 江苏保安员证【单选题】考试题库及答案
  • 71-Python+MySQL 医院挂号问诊管理系统-1
  • 图片重命名
  • 同网段通信ARP
  • WWDC25 苹果开发武林圣火令挑战:探索技术前沿,聆听创新故事
  • 深度解析大模型服务性能评测:AI Ping平台助力开发者精准选型MaaS服务
  • Blender 了解与学习
  • AI语音电话语音机器人的优点和缺点分别是什么?
  • 【阿里云PAI平台】 如何在Dify调用阿里云模型在线服务 (EAS)
  • 省钱自学版一次过阿里云ACP!!!
  • 建立了 abc 联合索引,where a = ? and b = ? order by c 能命中索引吗?
  • 携程线下面试总结
  • 【数据工程】9. Web Scraping 与 Web API
  • Vue3 emit和provide
  • linux C 语言开发 (十二) 进程间通讯--消息队列
  • 报考湖北安全员A证需要哪些条件?
  • olap和oltp类业务