如何以及何时分配全局或静态数组的内存?

时间:2011-06-01 18:04:10

标签: c++ arrays static global contiguous

在c ++中定义全局或静态数组时,它的内存不会立即保留在程序的开头,而只会在我们写入数组时保留。我发现令人惊讶的是,如果我们只写入数组的一小部分,它仍然不会保留整个内存。考虑以下小例子,它稀疏地写入全局数组:

#include <cstdio>
#include <cstdlib>

#define MAX_SIZE 250000000
double global[MAX_SIZE];

int main(int argc, char** argv) {
   if(argc<2) {
      printf("usage: %s <step size>\n", argv[0]);
      exit(EXIT_FAILURE);
   }
   size_t   step_size=atoi(argv[1]);

   for(size_t i=0; i<MAX_SIZE; i+=step_size) {
      global[i]=(double) i;
   }

   printf("finished\n"); getchar();
   return EXIT_SUCCESS;
}

现在针对不同的步长执行此操作,并查看top的输出,例如:

./a.out 1000000
./a.out 100000
./a.out 10000
./a.out 1000
./a.out 100

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
15718 user      20   0 1918m 1868  728 S    0  0.0   0:00.00 a.out
15748 user      20   0 1918m  10m  728 S    0  0.1   0:00.00 a.out
15749 user      20   0 1918m  98m  728 S    1  0.8   0:00.04 a.out
15750 user      20   0 1918m 977m  728 S    0  8.1   0:00.39 a.out
15751 user      20   0 1918m 1.9g  728 S   23 15.9   0:00.80 a.out

RES列表示内存仅以小块保留,这也意味着阵列不太可能在物理内存中连续。任何人都对更低层次的事情有了更多的了解?

这也有负副作用,我可以很容易地运行许多程序,其中所有VIRT的总和超过物理内存,只要RES的总和低于。但是,只要它们全部写入全局数组,系统就会耗尽物理内存,并且程序会发送sigkill或其他内容。

理想情况下,我想告诉编译器在开始时保留全局变量和静态变量的内存。可能的?

修改

@Magnus:线条实际上是正确的顺序。 :)以第一行为例./a.out 1000000意味着我在数组中每写第100个条目,因此总共只有250个。这相当于只有1868k的RES。在最后一个示例./a.out 100中,每写入一百个条目,然后总物理内存也被物理分配RES = VIRT = 1.9g。从数字看,每当条目写入数组时,物理内存上会保留一个完整的4k块。

@Nawaz:数组在虚拟地址空间中是连续的,但据我所知,操作系统可能很懒,只在实际需要时保留物理内存。由于这是在小块中完成的,而不是一次完成整个阵列,如何保证它在物理内存中是连续的?

@Nemo:非常感谢,确实在调用a.out的多个实例时,这些实例在开始时暂停,然后写入数组,我在oom-killer中收到/var/log/messages条消息,的确如此sysctrl命令阻止我首先启动太多a.out个实例。谢谢!

Jun  1 17:49:16 localhost kernel: [32590.293421] a.out invoked oom-killer: gfp_mask=0x280da, order=0, oomkilladj=0
Jun  1 17:49:18 localhost kernel: [32592.110033] kded4 invoked oom-killer: gfp_mask=0x201da, order=0, oomkilladj=0
Jun  1 17:49:20 localhost kernel: [32594.718757] firefox invoked oom-killer: gfp_mask=0x201da, order=0, oomkilladj=0

最后两行略显令人担忧。 :)

@doron:谢谢,很好的解释,抱歉不能upvote / select。

4 个答案:

答案 0 :(得分:6)

您正在查看正在提交的虚拟内存页面。操作系统通常只会在您的代码明确写入或读取页面时提交页面。这与C ++无关,后者保证数组是连续的。如果您在启动时询问如何让操作系统提交所有进程的页面,则需要使用特定于操作系统的内容(如果存在)。

答案 1 :(得分:2)

这里有两件事情可以发挥作用。虚拟内存和物理内存。

静态数据的虚拟内存,就像程序指令一样,在程序开始执行之前分配。通过这个我的意思是你的程序的地址总是被定义。

操作系统可能是懒惰的,但是当涉及将静态数据和程序指令加载到物理内存RAM中时。这种方式的工作原理如下:

  • 进程加载程序为静态数据分配了进程虚拟内存,但未将数据加载到RAM中。
  • 当试图访问这些地址时,会触发处理器异常,然后我们进入内核模式。
  • 内核现在将数据加载到RAM中,并将RAM链接到进程虚拟地址空间。
  • 内核切换回用户模式,直到处理器异常发生的确切位置。
  • 由于RAM现在已链接到进程虚拟地址空间,程序现在将继续执行,就好像什么也没发生过一样。

这是操作系统允许执行的一小部分操作,因为它对于正在运行的进程完全无法检测到。除非我们当然缺乏记忆。

答案 2 :(得分:1)

我不认为您发布的表证明了任何实质性内容。

就静态存储阵列而言,它是在程序启动之前分配的,根据定义,在程序进入main()函数之前,运行时将内存分配给全局数组,并持续计划的持续时间:

§3.7.1/ 1

  

所有既没有动态的物体   储存时间也不是当地的   静态存储时间。存储   因为这些物体应该持久   计划的持续时间(3.6.2,   3.6.3)。

无论是全局还是本地,数组总是具有连续的内存。

答案 3 :(得分:1)

这听起来像一个Linux系统,一旦使用的内存超过可用虚拟内存,“OOM杀手”就会唤醒并开始查杀进程。 grep for / var / log / messages中的“oom”确认。

如果是,则此设置:

sysctl -w vm.overcommit_memory=2

...将阻止内核允许您的进程分配比可用VM更多的内容。