【问题标题】:char * string = "Hello"; memory address space字符 * 字符串 = "你好";内存地址空间
【发布时间】:2013-12-25 17:40:31
【问题描述】:

这个板子似乎充满了这类问题,我确实回答了其中一些,但我仍然对这两者之间的区别感到困惑:

char string[] = "Hello"; 
char * string2 = "hello";

现在,

char string[] = "Hello"; 

... 是一个数组,分配了 6 个连续的内存地址空间来存储字符,包括末尾的 \0。

&string

%p 显示内存地址 0x7fffbcabce90。 string%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?

【问题讨论】:

  • 顺便说一句,我无法想象您还没有找到可以理解这一点的单个副本。我记得自己多次解释过这一点。
  • @H2CO3 能否请您给我一个示例来解释该内存地址范围?两个 cmets 吼道,你在告诉我:“@ChibiSlick 我不知道。你也不应该担心这个。 - 1 分钟前的 H2CO3。”你困惑吗? :)
  • 不,我不困惑,但这不再是 C。您正在询问系统的实施细节。而且我不擅长低级的东西,所以我不知道为什么链接器选择那个地址或者为什么0x4000000是只读的。

标签: c memory linker operating-system


【解决方案1】:
char string[] = "Hello"; 

声明一个以空结尾的char 数组并将其初始化为包含"Hello\0"

char * string2 = "hello";

声明一个指向char 的指针并将其初始化为指向一个字符串字面量。

修改字符串文字会调用未定义的行为。编译器将字符串文字放在只读内存中是很常见的,这似乎就是您的编译器正在做的事情。并非所有编译器都会这样做——关键是修改字符串文字会调用未定义的行为。

查看程序中指针的实际值几乎没有什么收获。只读地址接近0x00400000,因为我认为您在Windows 上运行,这是可执行模块的默认加载地址。但是没有什么说模块必须在那里加载。图书馆不会。


我们来看看:

printf("%p %p %p %p", string1, &string1, string2, &string2);

在这个表达式中,string1 衰减为一个指针,因此输出与&string1 相同。而string2 是一个指针,&string2 是那个指针的地址。

【讨论】:

  • 您还应该解释地址范围 - 正如问题中特别提到的那样。
  • @elyashiv 我该如何解释?这实际上取决于编译器和操作系统来决定存储的位置。您认为实际的指针值有什么意义?
  • @DavidHeffernan 我认为他指的是&array[0]&array的数值相同,而&pointer[0]&pointer的数值不同。
  • @H2CO3 这个问题甚至没有这么说。我想我们必须假设提问者的真正意思。
  • 我理解一个是数组,另一个是指针的部分。我还知道内存地址是不同的,当然它们是不同的,一个是数组,另一个是指针:) 我的询问是关于使用的内存范围 bing (0x400000)。这个范围是多少?为什么使用这个范围?它是只准备好的吗?这是我不能直接更改字符值的原因吗?我可以明确使用不同的范围吗?
【解决方案2】:

一个进程的虚拟地址空间的布局一般是由链接器和加载器来安排的。一般来说,进程的内存可以分为以下几种类型:

  • “文本”部分,其中包含执行的指令。
  • 只读数据,其中包含已读取但未修改或执行的值。
  • 初始化数据,在程序启动时设置为初始值,但可以在程序执行期间进行修改。
  • 未初始化的数据,程序需要用于工作空间但不需要在程序启动时设置为任何特定值。

对记忆的类型有各种修饰和细化,但以上内容足以作为大致方向。

分离不同类型的内存对于性能和安全性很重要,包括:

  • 由于文本和只读数据部分在程序执行期间不会被修改,如果同一程序同时执行多次,则可以共享它们。每次程序运行时,操作系统都可以将不同进程的虚拟地址空间的这些部分映射到同一个物理内存中。每个进程在这些部分中都将具有相同的数据。
  • 由于非文本部分永远不应作为指令执行,因此可以将它们标记为不可执行。这意味着如果尝试执行它们,硬件将导致异常,只有在存在错误或攻击者导致程序执行不应执行的操作时才会发生异常。

操作系统不会跟踪进程地址空间中的每个单独字节。大多数硬件不支持它,并且它需要太多数据。相反,有一个最小数量的内存,称为页,被使用。每当内存被标记为可执行或不可执行、可写或不可写时,都必须以整页为单位进行。 4096 字节是典型的页面大小,但因系统而异。

您在char string[] = "Hello";char *string2 = "Hello"; 的不同地址中看到的是字符串文字"Hello" 的只读数据被放入与使用@987654324 初始化的可修改数组不同的页面中@。

地址 0x400000 没有什么神奇之处,只是它被选为您正在使用的系统中的只读数据位置。它可能在其他地方。甚至可能有链接器选项将其移动到您选择的地址。重要的是它与可修改的数据是分开的。

当链接器读取目标模块并将它们组织成一个可执行文件时,它会连接来自不同目标模块的相同类型的段。也就是说,它从每个模块中获取文本段并将它们组合成一个大文本段。它从每个模块中取出只读数据段,并将它们放在一个大数据段中。等等。这比将每个对象模块的段作为单独的部分更有效——如果一个对象模块使用 2.5 页作为只读数据而另一个使用 1.5 页,那么将它们放在一起仅使用 4 页,而将它们与页面片段分开未使用将使用 5 页。 (某些特殊段的处理方式可能与串联不同。)

此外,虽然内存可以以页为单位进行管理,但将大量相同类型的内存组合在一起可能会有好处。如果将整个兆字节标记为具有相同的属性,则操作系统可能会使用更少的数据来跟踪它,而不是单独标记页面。这可能是链接器为某种类型的内存留出大量程序地址空间的部分原因。 (我在推测;我不熟悉您正在使用的特定操作系统的当前动机和设计。)

【讨论】:

  • 如果存在这样的事情,那将是一个史诗般的答案。同时,我会将您的答案标记为已接受的答案:) 谢谢 Eric!
  • 重要的是它与可修改的数据分开。不需要。它可能就在可修改内存旁边。它可能在同一页面中。
  • @DavidHefferan:我不确定你的意思。重要的是只读数据与可修改数据位于不同的页面中,以便可以使用不同的属性对其进行标记。只读数据和可修改数据不能在同一个页面;该页面只能是只读的或可修改的,不能两者兼而有之。也许你的意思是 C 标准允许字符串文字在可修改的内存中,但这不是这个答案的目的。
  • 我不太确定答案是什么。因为问题是关于 C 而不是一些具体的实现。我认为提问者并没有真正掌握标准所说的内容与具体实现之间的区别。有很多 C 实现写入字符串文字不会导致程序失败。甚至页面的概念都是特定于实现的,而且对于某些机器来说,这个概念没有任何意义。
  • @DavidHeffernan:问题标记为 C,但显然是关于内存布局的。 OP 想知道为什么字符串文字的地址与非文字数组对象的地址不同。我回答了他们。
【解决方案3】:

因为数组不是指针。

char string[] = "Hello"; 

string 声明为一个数组,并使用RHS 上的字符串字符对其进行初始化。这个数组没有声明为const,所以你可以修改它的元素。

当然,数组从它的第一个元素所在的内存开始(嗯,至少在我知道的任何合理的实现中,但这不是必需的),所以很自然地string(它会衰减为指向第一个元素)具有与&string 相同的数字指针值,它是指向数组本身的指针。不过它们有不同的类型(pointer-to-char 和 pointer-to-array-of-6-chars)。

这是它在内存中的样子:

+-----+-----+-----+-----+-----+-----+
| 'H' | 'e' | 'l' | 'l' | 'o' | \0  |
+-----+-----+-----+-----+-----+-----+
^
+-- pointer to first element
|
+-- also: pointer to array

另一方面,

char *string2 = "Hello";

错误:在这里,您将只读字符的指针(数组"Hello" 衰减到的指针)分配给指向非常量的指针。这应该是

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)

【讨论】:

  • 我想我花了足够的时间来证明我知道数组和指针之间的区别,我猜不是:) 你提到地址范围 0x400000 只是准备好了.. 我的困惑是关于这一点。这个范围是多少?为什么使用这个范围?它是只准备好的吗?这是我不能直接更改字符值的原因吗?我可以明确使用不同的范围吗?
  • @ChibiSlick 我不知道。你也不应该担心这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-03
  • 2023-03-21
  • 1970-01-01
  • 1970-01-01
  • 2017-01-18
  • 2015-07-10
相关资源
最近更新 更多