为什么全局变量存储在.data中而不是存储在过程映像的堆中?

时间:2016-10-28 14:35:58

标签: c assembly heap-memory

我正在学习操作系统基础测试,我想到了这一点。当我在C程序中声明一个全局(或静态)变量时:

char* msg = "Hello World!\n";    

.data中保留了一个字节数组,"Hello World!\n"字符串保存在.text中,然后当程序加载到内存并开始执行时,{{1使用msg中保存的字符串初始化var。这是怎么回事?那么,保留.text中的字节而不是堆中的字节有什么不同?我知道在.data中他们有一个静态大小,但他们也冷在堆中保留,对吧?为什么这些东西是分开的?在过程映像中只有堆,堆栈和代码部分而不是更多的分数会更有效吗?它可能不是因为物理内存被映射到多个虚拟地址(例如记事本的多个实例),因为这些变量是可编辑的。

提前谢谢

5 个答案:

答案 0 :(得分:1)

这个编译器的作用是使这个(常量)文字成为一个读/写变量。

编译器在.text中收集所有文字字符串。当一个文字字符串在程序中被多次使用时,它只会在.text中使用一次字面值。

启动时,它会将其复制到.data中的保留空间。这很有趣:

char msg[] = "Hello World!\n";
char *msg  = "Hello World!\n";

编译器将第一个文字从.text复制到.data是可以的;它是根据用户的指示初始化变量。

编译器将第二个文字复制到.data是不正确的:它应该已初始化*msg,指针指向.text中的文字和.text段应该被设为只读(由内存硬件管理,在尝试写入内存时导致异常)。

答案 1 :(得分:0)

"全球"通常意味着可以从任何地方访问"。

通常,人们通过将全局数据放在固定位置来完成汇编程序;那么任何需要访问的代码只需使用其地址直接引用它。这是通过将全局变量放在.data段中来实现的;链接器将为它们分配固定地址。

您可以考虑放置"全球数据"在堆中。如果你这样做,代码如何访问它?它不能知道数据在堆中的位置。这种代码知道这一点的唯一方法是传递一个指向"全局数据"作为一个参数(这意味着每个子程序必须接受这个指针并将其传递给所有被调用者;这真的很不方便),或者代码必须知道哪里有指向堆的指针代码可以直接访问(指针必须有一个固定的地址才能找到,因此指针本身就是全局数据)。拥有这样的指针意味着现在对全局数据的访问总是需要间接,这会降低代码的速度。所以,如果你这样做,你最终会得到一个尴尬而缓慢的方案来访问" global"数据。 (大多数人不会调用堆中分配的数据"全局数据")。

所以......全球数据放在易于访问的地方。在数据段中。

如果您的全局数据是常量且不会更改,则可以将其放在数据段中。将这些数据放在文本段中可以确保,对于大多数现代操作系统来说,这些数据是写保护的,强制执行“不会改变”#34;假设。这有助于发现程序中的错误。

答案 2 :(得分:0)

简短的回答是,textdata区域由加载程序初始化。 C程序的堆存储(通常)是malloc()及其亲属返回的内容。它不是未初始化并且由OS根据需要提供,而不是在程序加载时提供。这就是为什么它也被称为动态存储。

通过将编译后的二进制文件中的信息复制到操作系统分配给加载程序的存储中来初始化像字符串这样的文字常量。您提供的段名称.text.data是编译的二进制文件中用于标记此类信息的约定。不同的风格告诉操作系统应如何在VM空间中映射副本 - 如只读,读写等。

我上面所说的只是典型的。编译器决定如何在操作系统和硬件提供的内存模型中存储C值。您的问题和预期答案仅与您正在使用的编译器和操作系统相关。

该行

char* msg = "Hello World!\n";

声明初始化的全局char指针值可以通过多种方式进行编译。但最常见的是在只读存储(Linux Hello World~\n\0)中分配文字字符串.text。 C允许但不要求文字字符串受到保护,以便像

这样的代码
char* msg = "Hello World!\n";

...
{
  ...
  char *p = msg;
  strcpy(msg, "foo"); 
  printf("%s", p);

会引发错误(通常是段违规),而不是打印foo。并非所有OS /编译器配置都会这样做。例如,嵌入式系统可能不支持只读存储,因此上面的代码可能会运行,但在此之后,C标准没有指定程序将在以后执行的任何操作。

编译器需要做的另一件事是分配和初始化msg的全局值。这通常是一个2字节,4字节或8字节指针,同样取决于编译器,操作系统和硬件。必须将其设置为文字字符串的地址。常见的方法是在二进制文件中发出符号,允许加载程序在程序启动时注意读写存储(Linux .data)中的初始化。但还有其他选择:固定偏移和启动代码是两个。

答案 3 :(得分:0)

在运行时执行的具有静态存储持续时间(SSD)的对象的任何类型的初始化通常称为动态初始化(借用C ++术语)。 SSD对象的动态初始化无论多么有用,都会带来许多问题。例如,这种初始化的最终结果可能取决于它的执行顺序。初始化可能会因某些不可预测的原因(内存不足?)而失败。显然,SSD对象的值不能被视为常量表达式。

同时,为了避免出现这些问题,C语言规范被有意识地设计为避免对SSD对象进行动态初始化的任何需要。例如,C程序中的所有这些对象都需要使用常量值进行初始化。这意味着这些对象的初始值没有任何初始化顺序依赖性,并且在编译时在概念上是已知的#34;。当C程序启动时,所有SSD对象都会以正确预先指定的值开始生命,不需要额外的运行时初始化。

然而,实际情况更复杂,并且在编译时并不总是可以预测SSD对象的初始值。例如,描述可重定位目标文件位置的地址常量只在加载时才知道,而不是在编译时才知道,这通常使得无法在编译时预测SSD指针的值。语言规范旨在适应这种特殊性。故意限制C中地址常量的属性,以便稍后在加载时初始化它们的值。在C中,当在编译时未知初始值时,这被认为是适当的折衷,但我们仍然希望保持SSD对象的外观在其初始化状态下开始。

请注意,语言规范并不要求以特定方式实现。它说

  

应初始化具有静态存储持续时间的所有对象(设置为   程序启动前的初始值。的方式和时间   否则这种初始化是未指定的。

这意味着很可能提出一个有效的实现,它将使用堆内存为SSD对象分配存储并满足语言规范的所有其他要求。但历史上,C实现通常努力避免运行时初始化,并且倾向于依赖于初始化的数据段(如.data)和一些基本的加载器功能。同样,语言规范是专门为这种SSD对象支持方法量身定制的。

答案 4 :(得分:0)

.data仅为指针(指针变量msg)提供空间,"Hello, world!"字符串继续位于.text段中,并且不会在任何地方复制任何内容。 msg变量(存储在.data段中的指针)只是指向字符数组所在的.text段中的位置的指针(该数组只读)。只是尝试修改数组中的某些内容,例如msg[3] = '\0';并查看它是如何失败的(尽管msgchar *而不是const char *