假设有一个函数store_at(int)
应该将传递的数字存储在给定的十六进制位置,如下所示:
void store_at(int val)
{
int *ptr;
ptr = (int *)0x261;
// logic goes here
return;
}
我们如何编写逻辑以将 val 存储在给定的十六进制位置(在本例中为 0x261)?
说*ptr = val;
有用吗?我隐约记得在某处读到 C 中不允许这样做。
*ptr = val;
作品。但是您必须确保已分配此地址,并且可以访问更多地址。在不知道您正在编写 C 的情况下,我可以建议一些防止访问您没有权限的地址的方法。因此,这在很大程度上取决于您使用的架构和/或操作系统。
例如,在 ATMEGA32 微控制器中,您对其主存储器的访问没有任何限制。您可以从中/为它读取、编写和执行代码:
PORTB = 1;
// Knowing that PORTB is stored at 0x38, you can do the equivalent:
*((unsigned int *)0x0038) = 1;
但那是在嵌入式系统上。现在,如果您想要完全访问内存空间(只要它在您的应用程序沙箱中),您可以VirtualProtect
在 Windows 和mprotect
Linux 上使用:
int val = 123;
DWORD oldprotection;
VirtualProtect((LPVOID)0x261, sizeof(int), PAGE_EXECUTE_READWRITE, &oldprotection);
*(int *)0x261 = val;
以下是您可以使用的保护类型:内存保护常量。
还有一个mprotect
例子:
int val = 123;
mprotect((const void *)(((int)(0x261) / PAGESIZE) * PAGESIZE), sizeof(int), PROT_WRITE | PROT_READ | PROT_EXEC);
*(int *)0x261 = val;
请注意,此mprotect
示例未经测试,您可能需要增加大小以进行保护或其他一些事情。
除以PAGESIZE
那里只是一个正确对齐地址的技巧。PAGESIZE
另请注意,您的地址对 Linux 无效,因为如果大于它,它的除法将导致 0 (与"it will be"相同)。
根据使用指针访问地址的语法,所有这些工作:
*(int *)0x261 = val;
int *ptr = (int *)0x261;
*ptr = val;
是的,表达式*ptr = val
(甚至更多,*(int *)0x261 = val;
)在 C 中完全有效。但是您将面临运行时环境的技术限制。
现代操作系统通常在虚拟内存沙箱中运行进程(因此进程无法访问和破坏其他进程的内存),从技术上讲,进程的虚拟内存看起来像一组您可以访问的区域,其中一些以只读方式, 有些不允许从这里执行代码等等。当您尝试访问不可用的 VM 区域时,您将遇到SIGSEGV
类 Unix 系统或Access Violation
Windows 系统上的错误,写入只读内存区域并尝试在操作系统禁止的区域中执行代码也是如此(例如,您可以使用 pid in 看到 linux 进程的虚拟内存映射/proc/$PID/maps
。
进程的内存通常由操作系统管理(您可以使用操作系统提供的函数从堆中获取新内存,例如malloc()
, calloc()
;堆栈内存区域由操作系统在进程启动时分配),因此在用户空间编程中,您几乎永远不会需要通过字面量指针来引用数据。
另一种可能的环境是内核空间或裸机 C 程序,您可以在其中拥有所有可用的物理内存,但您仍然必须了解您访问的内容(可能是端口、物理内存中的间隙,也可能是由硬件等保留)。对这样的环境进行编程是一个高级主题,需要良好的 C 经验。