char * string =“你好”;内存地址空间

时间:2013-12-08 17:39:52

标签: c memory linker operating-system

这个董事会似乎充满了这些类型的问题,我确实经历了其中的几个问题,但我仍然对这两个问题之间的区别感到困惑:

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解释了存储文字在内存中的位置:

String literals: Where do they go?

3 个答案:

答案 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)