C++对象指针无法进行-n或+n [包含VS Code启动调试教程]
Gcc版本:gcc (Rev3, Built by MSYS2 project) 12.1.0
VS Code版本:
版本: 1.70.1 (system setup)
提交: 6d9b74a70ca9c7733b29f0456fd8195364076dda
日期: 2022-08-10T06:08:33.642Z
Electron: 18.3.5
Chromium: 100.0.4896.160
Node.js: 16.13.2
V8: 10.0.139.17-electron.0
OS: Windows_NT x64 10.0.22000
最近在学 C++,发现对象数组指针无法进行 + n 或 -n操作,如果进行 +n或者 -n操作,那么输出就会异常。
先说解决办法:使用++,--代替 +n 或 -n 操作。
Bug复现
1.定义一个类
2.在堆上分配对象数组
3.给三个对象的成员变量赋值
4.打算用 for 循环输出,所以直接对指针 -2,将指针复原
5.使用 for 输出
很显然,没有得到我们想要的结果。这是为什么呢?
调试
通过观察,发现输出的 "葫芦娃" 是最后一个初始化的对象成员变量,有没有一种可能——指针没有向后移动两个对象长度。
1.配置默认生成任务[单个源文件]
说干就干,我们使用 VS Code 调试本程序。依次点击 终端>配置默认生成任务 ,在下拉框选择 C/C++:g++.exe 生成活动文件

系统会帮我们自动生成默认配置:
并且在资源管理器的工作空间下自动生成 tasks.json 文件

2.配置调试文件
点击侧边栏的运行和调试按钮,然后点击 创建launch.json 文件

在下拉栏中选择 C++(GDB/LLDB)

点击右下角的添加配置

在弹出的下拉栏中选择 C/C++: (gdb) 启动 选项

系统会自动生成调试器配置文件模板,我们需要修改几个东西。

第一,在 "program":后输入字符串 "${fileDirname}\\${fileBasenameNoExtension}.exe" ,${fileDirname} 表示当前激活文件的目录名称,\\ 在windows平台代表 / 文件夹分隔符,${fileBasenameNoExtension} 表示当前激活文件的名字,不包括扩展名。
第二,在 "miDebuggerPath": 后输入你 gdb.exe 所在的完整目录,这是你调试器的路径。举例:我的目录如下

所以填写:"miDebuggerPath": "D:\\msys64\\mingw64\\bin\\gdb.exe" ,你也可以把 \\ 换成 / "miDebuggerPath": "D:/msys64/mingw64/bin/gdb.exe" 都是可以的。
第三,在这个花括号内,添加最后一个选项 "preLaunchTask": 这个选项表示:在启动调试之前,先启动一个任务;由于我们选择的是启动调试,所以源文件需要提前编译好,再将编译好的文件进行启动调试,而这个提前的任务就是我们刚才设置的默认生成任务。

现在,打开 tasks.json 文件找到 "label",复制后面的字符串到 "preLaunchTask":后面。

3.调试
在开始调试之前,先回到我们的源文件,还有几件事没做。
1.更改字符集;调试器的默认编码是 utf-8 , 如果以现在的字符集去生成可执行文件,在调试输出时将产生乱码。点击右下角状态的字符集,我的默认字符集是 GB 2312 ,现在点击它

选择通过编码保存

在弹出的字符集中选择 utf-8

2.设置断点。只有设置断点,调试器才会根据断点停止,否则直接完成运行程序,那调试也无从说起了。在 VS Code中设置断点和 VS 中是一样的:在行号的左边,鼠标左键单击,出现红色圆点,就代表断点已经设置完成。

3.开始调试。点击左边栏的 启动和调试 按钮,现在可以看到我们配置的启动调试了。点击小三角形,开始调试。它将依次生成可执行文件.exe,然后执行启动调试。注意:必须在被激活的文件上执行调试(也就是你想要调试的文件上)。

此时程序停在了断点处:

调试面板位于屏幕上方中间处:

~@ 点击最左边的六个小点可以拖动调试面板
~@ 第一个一个竖线挨着一个三角形的按钮,表示继续执行程序按钮,它的作用是执行到下一个断点,再停止程序。
~@ 第二个按钮,像跳过一个点的按钮,表示跳过函数内部执行过程,如跳过 Aclass[0].say(); 函数,直接运行到第43 行。

~@ 第三个按钮,一个竖向的箭头指向一个点,表示单步执行,它的作用是,以精确的程序执行步骤运行程序,在调试过程中遇到函数,则会进入那个函数内部,一步一步执行每一行代码,只有执行完那个函数内部的代码才会回到上一层函数的栈上来。
~@ 第四个按钮,一个点位于一个竖向向上箭头的底部。表示单步跳出,能够从函数内部直接跳到上一层函数栈上来。
~@ 第五个按钮,一个带箭头的圈圈,表示重启调试过程。
~@ 第六个按钮,一个正方形图像,表示停止调试。
4.添加监视。在左边栏中可以看到本地变量的名称和地址:

右键点击我们要监视的变量 Aclass,在弹出的菜单中选择 添加到监视

在变量窗口下,就是监视窗口。在 Aclass的左边有一个 > 按钮,点击它,可以展开详细的信息,比如该变量的成员信息,地址信息:

回到正题,我们要验证——使用地址 -n改变不了当前地址。点击单步执行,监视窗口刷新了变量信息,右键单击变量名,点击复制值

1) 我们记录第一次变量的地址信息:
2) 运行到 变量 ++ 之后,记录第二次变量地址的信息。

地址:
3) 记录最后一次 变量 ++ 地址改变信息:
不难发现三个地址相差了 0x 10
4) 记录 变量 -2 时地址改变的信息:

地址:
通过对比,我们发现运行 Aclass-2并不会改变变量的地址,问题出在这。
我们将Aclass-2; 这行代码换成两行 Aclass--;然后再调试一次,发现地址变化出现了预期效果。
结果告诉我们,在现版本的C++中已经不支持使用 -n、+n这种方法执行地址操作了。使用++,--依旧有效。
源码展示:
文章存在歧义是必然的,此时应请提供:gcc版本号,VS code版本号,供大家斟酌仔细分析。