|
|
发表于 2026-7-12 11:01:33
|
显示全部楼层
广东省广州市
这个困惑很常见,说明你已经理解了指针的基本概念,差的是「什么时候非用不可」的场景。
先说一句:日常小练习里确实可能几乎用不到指针;但一旦涉及 动态内存、回调、数据结构、系统 API、多返回值,指针就绕不开。易语言里很多操作(传址参数、GetWindowText 填缓冲区、读内存、调 DLL)底层也是指针,只是 IDE 帮你封装了。
一、什么时候「必须」用指针?
场景 为什么必须用
1. 让函数修改调用者的变量
C 语言默认传值,函数里改的是副本
2. 动态分配内存
malloc/new 返回的就是堆内存地址
3. 传递大结构体
传指针避免整份拷贝,省内存和时间
4. 链表/树等数据结构
节点之间靠地址链接,没法用普通变量串联
5. 函数回调
把函数地址传给系统/API(如 qsort、WinAPI 回调)
6. 调用系统 API
Windows/Linux API 大量用指针传缓冲区、结构体
下面用一个完整小程序,把前 4 种场景串起来。
二,示例:
完整源码
.版本 2
.程序集 窗口程序集_启动窗口
.子程序 __启动窗口_创建完毕
演示_取变量数据地址 ()
演示_ReadProcessMemory读本进程 ()
' 演示_ReadProcessMemory读外部进程 () ' 可选,需目标进程在运行
' ============================================================
' 场景1:取变量数据地址 —— 易语言里的“指针”
' 作用:拿到变量在内存中的地址,后续可传给 API 或读写内存
' ============================================================
.子程序 演示_取变量数据地址
.局部变量 分数, 整数型
.局部变量 名称, 文本型
.局部变量 地址, 整数型
.局部变量 临时值, 整数型
分数 = 85
名称 = “精易论坛”
地址 = 取变量数据地址 (分数)
调试输出 (“========== 场景1:取变量数据地址 ==========”)
调试输出 (“分数 = ”, 分数)
调试输出 (“分数变量地址 = ”, 十到十六 (地址))
' 从地址读出 4 字节整数(相当于 C 的 *p)
RtlMoveMemory (取变量数据地址 (临时值), 地址, 4)
调试输出 (“从地址读出的值 = ”, 临时值)
' 往地址写入新值(相当于 C 的 *p = 100)
临时值 = 100
RtlMoveMemory (地址, 取变量数据地址 (临时值), 4)
调试输出 (“通过地址修改后,分数 = ”, 分数) ' 输出 100
调试输出 (“名称变量地址 = ”, 十到十六 (取变量数据地址 (名称)))
调试输出 (“”) ' 空行
' ============================================================
' 场景2:ReadProcessMemory 读本进程内存
' 作用:从指定内存地址读取字节到缓冲区
' 关键:第3参数必须传缓冲区的【内存地址】,不是传文本内容本身
' ============================================================
.子程序 演示_ReadProcessMemory读本进程
.局部变量 源文本, 文本型
.局部变量 源地址, 整数型
.局部变量 缓冲区, 字节集
.局部变量 进程句柄, 整数型
.局部变量 已读字节, 整数型
.局部变量 成功, 逻辑型
源文本 = “Hello Pointer!”
源地址 = 取变量数据地址 (源文本)
缓冲区 = 取空白字节集 (64)
已读字节 = 0
' GetCurrentProcess() 返回 -1,可读本进程内存
进程句柄 = -1
调试输出 (“========== 场景2:ReadProcessMemory ==========”)
调试输出 (“源文本 = ”, 源文本)
调试输出 (“源文本地址 = ”, 十到十六 (源地址))
成功 = ReadProcessMemory (进程句柄, 源地址, 取变量数据地址 (缓冲区), 64, 取变量数据地址 (已读字节))
.如果真 (成功)
调试输出 (“读取成功,字节数 = ”, 已读字节)
调试输出 (“读到的内容 = ”, 到文本 (缓冲区))
.否则
调试输出 (“ReadProcessMemory 失败,错误码 = ”, 取最后错误 ())
.如果真结束
调试输出 (“”)
' ============================================================
' 场景3(可选):ReadProcessMemory 读外部进程
' 典型用途:读游戏/软件内存、配合 CE 找地址
' 注意:需要 PROCESS_VM_READ 权限,部分进程需管理员运行
' ============================================================
.子程序 演示_ReadProcessMemory读外部进程
.局部变量 进程ID, 整数型
.局部变量 进程句柄, 整数型
.局部变量 目标地址, 整数型
.局部变量 缓冲区, 字节集
.局部变量 已读字节, 整数型
' 示例:读取记事本进程(先打开一个 notepad.exe)
进程ID = 进程_取ID (“notepad.exe”) ' 精易模块;无模块可用 API 枚举进程
.如果真 (进程ID = 0)
调试输出 (“未找到 notepad.exe,请先打开记事本再试”)
返回 ()
.如果真结束
进程句柄 = OpenProcess (#PROCESS_VM_READ, 假, 进程ID)
.如果真 (进程句柄 = 0)
调试输出 (“OpenProcess 失败”)
返回 ()
.如果真结束
' 目标地址需用 CE 等工具查找,这里仅作格式演示
目标地址 = 十六到十 (“00400000”)
缓冲区 = 取空白字节集 (256)
已读字节 = 0
.如果真 (ReadProcessMemory (进程句柄, 目标地址, 取变量数据地址 (缓冲区), 256, 取变量数据地址 (已读字节)))
调试输出 (“外部进程读取成功,字节数 = ”, 已读字节)
调试输出 (到文本 (缓冲区))
.否则
调试输出 (“读取失败,地址可能无效或无权限”)
.如果真结束
CloseHandle (进程句柄)
' ============================================================
' 以下 DLL 声明
' ============================================================
.DLL命令 RtlMoveMemory, , "kernel32", "RtlMoveMemory"
.参数 目标地址, 整数型
.参数 源地址, 整数型
.参数 长度, 整数型
.DLL命令 ReadProcessMemory, 逻辑型, "kernel32", "ReadProcessMemory"
.参数 进程句柄, 整数型
.参数 内存地址, 整数型
.参数 缓冲区地址, 整数型 ' 传 取变量数据地址(缓冲区)
.参数 读取长度, 整数型
.参数 实际读取, 整数型, 传址 ' 传 取变量数据地址(已读字节)
.DLL命令 OpenProcess, 整数型, "kernel32", "OpenProcess"
.参数 访问权限, 整数型
.参数 继承句柄, 逻辑型
.参数 进程ID, 整数型
.DLL命令 CloseHandle, 逻辑型, "kernel32", "CloseHandle"
.参数 对象句柄, 整数型
.常量 PROCESS_VM_READ, "1024", , , 读进程内存权限
运行说明
新建易语言程序,把上面代码贴进去(场景3 可注释掉)。
按 F5 运行,在调试窗口查看输出。
场景1 会证明:取变量数据地址 拿到地址后,可以通过 RtlMoveMemory 直接改原变量。
场景2 会证明:ReadProcessMemory 的第3参数必须是 缓冲区地址,这就是 API 层面对指针的要求。
预期调试输出大致如下:
========== 场景1:取变量数据地址 ==========
分数 = 85
分数变量地址 = 0019FFxx
从地址读出的值 = 85
通过地址修改后,分数 = 100
========== 场景2:ReadProcessMemory ==========
源文本 = Hello Pointer!
源文本地址 = 0019FExx
读取成功,字节数 = xx
读到的内容 = Hello Pointer!
三、一句话总结
指针 = 内存地址。当你需要「共享同一块内存、动态申请、链接数据、或对接系统 API」时,就用指针。
建议:先把上面示例编译跑一遍,再试着自己写一个「向链表插入节点」或「用指针实现字符串反转」,比背概念有用得多。
有问题可以继续追问,祝学习顺利。 |
|