这个董事会似乎充满了这些类型的问题,我确实经历了其中的几个问题,但我仍然对这两个问题之间的区别感到困惑:
char string[] = "Hello";
char * string2 = "hello";
现在,
char string[] = "Hello";
...是一个数组,分配了6个连续的存储器地址空间,用于存储字符,包括最后的\ 0。
& string 的%p 显示内存地址0x7fffbcabce90。 字符串的%p 显示相同的内存地址0x7fffbcabce90。 string [1] 的%p 显示0x7fffbcabce90 + typeof(char),因此为0x7fffbcabce91。等
char * string2 = "hello";
...是一个指向char类型的指针,它指向字符串第一个字符的内存地址(h)。
& string2 的%p 显示内存地址0x7fffbcabce88。 string2 的%p 显示不同的内存地址0x400ca8。 string2 [1] 的%p 显示0x400ca8 + sizeof(char),所以0x400ca9。
我的问题是:这个内存地址范围是什么(0x400000)?这是我无法修改字符串字符的原因吗?:
string[1] = 'c'; //that works
string2[1] = 'c'; //not working
谢谢!
编辑:拼写错误(%string2 =>& string2)
Edit2:正如我所解释的那样,关键字是字符串 literal 。
char * string2[] = "Hello";
...是指向字符串文字的指针。这是一个线索,其中R Samuel Klatchko解释了存储文字在内存中的位置:
答案 0 :(得分:6)
char string[] = "Hello";
声明以char
为单位的以null结尾的数组,并将其初始化为包含"Hello\0"
。
char * string2 = "hello";
声明指向char
的指针并将其初始化为指向字符串文字。
修改字符串文字会调用未定义的行为。编译器将字符串文字放在只读内存中是很常见的,看起来这就是编译器正在做的事情。并非所有编译器都会这样做 - 关键是修改字符串文字会调用未定义的行为。
通过查看程序中指针的实际值几乎无法获得。只读地址接近0x00400000
,因为我认为,您在Windows上运行,这是可执行模块的默认加载地址。但没有人说模块必须在那里加载。图书馆不会。
我们来看看:
printf("%p %p %p %p", string1, &string1, string2, &string2);
在此表达式中,string1
衰减为指针,因此提供与&string1
相同的输出。 string2
是一个指针,&string2
是指针的地址。
答案 1 :(得分:2)
进程的虚拟地址空间的布局通常由链接器和加载器来安排。通常,进程的内存可以组织成类型,包括:
对记忆类型有各种修饰和改进,但上述内容足以满足一般方向。
分离不同类型的内存对性能和安全性非常重要,包括:
操作系统不跟踪进程地址空间中的每个字节。大多数硬件不支持它,并且它需要太多数据。相反,使用的内存最少,称为页面。每当内存被标记为可执行或不可执行,可写或不可写时,必须以整页为单位进行。 4096字节是典型的页面大小,但它因系统而异。
您在char string[] = "Hello";
和char *string2 = "Hello";
的差异地址中看到的是,字符串文字"Hello"
的只读数据被放入不同于用"Hello"
初始化的可修改数组。
地址0x400000没有任何神奇之处,只是它被选为您正在使用的系统中只读数据的位置。它可能在其他地方。甚至可能有链接器选项将其移动到您选择的地址。重要的仅仅是它与可修改的数据分开。
当链接器正在读取对象模块并将它们组织成一个可执行文件时,它会连接来自不同对象模块的相同类型的段。也就是说,它从每个模块中获取文本段并将它们组合成一个大的文本段。它从每个模块获取只读数据段,并将它们组合成一个大型数据段。等等。这比将每个对象模块的段作为单独的部分更有效 - 如果一个对象模块使用2.5页用于只读数据而另一个使用1.5页,那么将它们放在一起仅使用4页,而将它们与页面片段分开未使用的将使用5页。 (某些特殊段可能以不同于串联的方式处理。)
此外,尽管可以以页为单位管理存储器,但将相同类型的大量存储器分组在一起可能是有益的。如果将整个兆字节标记为具有相同的属性,则操作系统可能会使用较少的数据来跟踪它,而不是单独标记页面。这可能是链接器为某种类型的内存留出大量程序地址空间的部分原因。 (我在推测;我不熟悉您正在使用的特定操作系统的当前动机和设计。)
答案 2 :(得分:0)
因为数组不是指针。
char string[] = "Hello";
将string
声明为数组,并使用RHS上字符串的字符对其进行初始化。此数组未声明为const
,因此您可以修改其元素。
当然,数组从第一个元素所在的内存开始(好吧,至少在我知道的任何合理的实现上,但这不是必需的),所以自然string
(它衰变成指针到第一个元素)具有与&string
相同的数字指针值,它是指向数组本身的指针。但它们有不同的类型(指向 - char
和指向数组的指针 - 6 - char
)。
这就是它在内存中的样子:
+-----+-----+-----+-----+-----+-----+
| 'H' | 'e' | 'l' | 'l' | 'o' | \0 |
+-----+-----+-----+-----+-----+-----+
^
+-- pointer to first element
|
+-- also: pointer to array
另一方面,
char *string2 = "Hello";
错误:在这里,您将指向只读字符的指针(数组"Hello"
衰变到的指针)指向非const指针。这应该是
const char *string2 = "Hello";
现在string2
是一个指针,它与其指向值(数组的第一个元素)具有单独的存储空间。所以很自然地,它的地址与数组的第一个元素(它指向的)的地址不同。并且它不是初始化与数组。数组(字符串文字)(推测)放在只读存储器中;标准说它试图修改其内容是未定义的行为。
这是带指针的构造在内存中的布局:
+-----+ +-----+-----+-----+-----+-----+-----+
| p | ---------> | 'H' | 'e' | 'l' | 'l' | 'o' | \0 |
+-----+ +-----+-----+-----+-----+-----+-----+
^ ^
+- adddress of +- address of the array (and the first element)
the pointer This is the **value** that is stored in p
itself
(completely
unrelated to
the address
of the array)