我发现在程序的数据段(来自SO)中放置了一个常量字面值,并且是只读的,因此行" s [0] =' a' "会导致错误,当我取消注释该行并运行时,实际上确实发生了错误。但是,当我查看MS VS中的内存窗口时,变量全部放在内存中。我很好奇他们(编译器)如何强制执行对“' s”的只读访问?
#include <iostream>
int main(void)
{
char *s = "1023";
char s_arr[] = "4237";
char *d = "5067";
char s_arr_1[] = "9999";
char *e = "6789";
printf("%c\n", s[0]);
// s[0] = 'a'; This line would error out since s should point to data segment of the program
printf("%s\n", s);
system ("pause");
}
0x002E54F4 31 30 32 33 00 00 00 00 34 32 33 37 00 00 00 00 1023....4237....
0x002E5504 35 30 36 37 00 00 00 00 39 39 39 39 00 00 00 00 5067....9999....
0x002E5514 36 37 38 39 00 00 00 00 25 63 0a 00 25 73 0a 00 6789....%c..%s..
0x002E5524 70 61 75 73 65 00 00 00 00 00 00 00 43 00 3a 00 pause.......C.:.
编辑1: 更新存储在s_arr中的值(应放在堆栈空间中),以明确它与字符串常量相邻。
编辑2:因为我看到有关基于页面的ro / rw访问的答案, 这里地址.. 0x ... 4f4是rw 0x ... 4fc是 ro 并且再次0x ... 504是rw。他们如何实现这种粒度?此外,由于每个页面可能至少为4kb,因此可以认为0x4fb可能是前一个ro页面的最后一个地址。但是我现在添加了一些变量来表明它们都是连续放置在内存中的,并且粒度是每8个字节。 你可以说,因为你提到的页面是4k级别,
答案 0 :(得分:7)
我不知道是什么让你认为你的例子显示了不可修改的内存旁边的可修改内存。你在说什么“粒度”?你的内存转储没有显示那样的东西。
您在内存转储中看到的字符串"4237"
不您的s_arr
。您看到的"4237"
有一个只读字符串文字,用作s_arr
的初始值设定项。该初始化程序已复制到s_arr
。同时,实际的s_arr
驻留在其他地方(在堆栈中)并且是完全可修改的。它也包含"4237"
(作为其初始值),但这是一个完全不同的"4237"
,您在内存转储中看不到它。让您的程序打印s_arr
的地址,您将看到它远远超出您转储的内存范围。
同样,您对“0x ... 4f4的声明是rw 0x ... 4fc是ro并再次为0x ... 504是rw”完全不正确。所有这些地址都是只读。它们都不是读写的。那里没有任何“粒度”。
请记住这样的声明
char s_arr[] = "4237";
实际上相当于
const char *unnamed = "4237";
char s_arr[5];
memcpy(s_arr, unnamed, 5);
在内存转储中,您正在查看上面示例中的unnamed
地址。该内存区域是只读的。您的s_arr
位于完全不同的内存区域,即读写。
答案 1 :(得分:2)
由于引入了32位平台,所有内容都放在同一个网段中(事实并非如此,但更容易认为是这样。有一些小问题需要几页解释,它们适用于操作系统设计)。
32位地址空间分为几页。 Intell允许使用页面粒度分配RO位。调试器仅显示32位(64位)地址,从技术上讲,该地址是段中的偏移量。调用这个偏移的简单地址是可以的。这不会有任何错误。
然而,链接器将不同的内存区域称为段。这些段与英特尔内存段无关。链接器段(代码,数据,堆栈等)被加载到diffrenet页面中。这些页面具有不同的属性(RO / RW,执行权限等)。
答案 2 :(得分:0)
您正在显示的内存块是存储字符串常量的区域(因为您可以看到所有4个值都直接存在于另一个旁边)。此区域标记为只读。在Windows上,每个4Kb内存块(页面)可以有自己的属性(读/写/执行),因此即使2个相邻位置也可以有不同的访问标记。
变量所在的区域位于不同的位置(在您的情况下为堆栈)。您可以通过检查&s
即时窗口(或观察窗口)的值来查看它。