您的当前位置:首页正文

冰冰学习笔记:内存地址空间

2024-11-12 来源:个人技术集锦

系列文章推荐


目录


前言

1.地址空间的分配方式

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int un_gval;
int g_val=100;                                                              
int main(int argc,char*argv[],char* env[])
{
    char* p= "hello";
      // 10;
      // 'a';
      //int val=0;
     static int val=0;
     printf("code addr :%p\n",main);
     printf("init global addr:%p\n",&g_val);
     printf("uninit flobal addr:%p\n",&un_gval);
     char*heap_mem1=(char*)malloc(10);
     char*heap_mem2=(char*)malloc(10);
     char*heap_mem3=(char*)malloc(10);
     printf("heap_mem1  addr:%p\n",heap_mem1);
     printf("heap_mem2  addr:%p\n",heap_mem2);
     printf("heap_mem3  addr:%p\n",heap_mem3);
     printf("static_val:%p\n",&val);
     printf("str:%p\n",p);
     printf("stack_mem1  addr:%p\n",&heap_mem1);
     printf("stack_mem2  addr:%p\n",&heap_mem2);
     printf("stack_mem3  addr:%p\n",&heap_mem3);
     int i=0;
     for( i=0; i<argc; i++)
     {
          printf("argv[%d]:%p\n",i,argv[i]);
     }
     for( i=0;env[i];i++)
     {
         printf("env[%d]:%p\n",i,env[i]);
     }
   
     return 0;
 }

        这里还有几个值得注意的小知识点:

(1)堆、栈相对而生

 (2)static修饰局部变量本质是将局部变量的存储位置更改,从栈区改到全局数据区。

 (3)堆区连续申请的空间之间彼此不会连续

        这是因为,操作系统开辟空间的时候会多开辟一部分空间来存储这块堆区申请空间的属性信息,比如大小等,所以连续开辟的空间之间不会紧挨着,会有空间来存储属性。

(3)字面常量代码可以编过,字符串存储在代码区

        代码中直接输入字符串,常数等语句编译是可以通过的,其中常量字符串存储在代码区。

 注意:

        以上内容默认只在Linux下有效,Windows不同。

2.虚拟内存地址与物理内存地址

        如果你的答案是:没错就是这么存储的。那请看下面的例子。

int g_val=100;
int main(int argc,char*argv[],char* env[])
{                                                                                                       
     pid_t id = fork();
     if(id==0)
     {
         //child
         int i=0;
         while(1)
         {
               
             printf("i am child,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n", \                 
                    getpid(),getppid(),g_val,&g_val);
             sleep(1);
             i++;
             if(i==3)
             {
                  g_val=200;
                  printf("child change g_val:100->200,g_val:%d\n",
                           g_val);
             }
          }
     }
     else if (id>0)
     {
         //father
         while(1)
         {
              printf("i am father,pid:%d,ppid:%d,g_val:%d,&g_val:%p\n",\
                    getpid(),getppid(),g_val,&g_val);
               sleep(1);
          }
     }
     else
     {
         printf("进程创建失败\n");
     }
    return 0;
}

3.什么是进程地址空间

        因此内存中数据的存储实际上是下图这样。

4.深入理解虚拟地址

        答案是有的!

5.为什么要有虚拟地址空间

(1)安全性

        那么是如何拦截的呢?

(2)提高效率

        是的!!!

        操作系统在执行这些工作的时候是自动进行的,用户以及进程完全0感知!!!这种延迟分配的策略让内存的使用率几乎达到100%,内存使用率高了,计算机的效率也就极大的提升了。

        换言之,如果不执行这种策略,当我们的进程申请了空间操作系统直接分配给我们,如果我们一直没有使用这块空间,那么操作系统分配给我们的这块空间是不是就浪费了呢?是的,只要你不释放,他就一直在那里,其他进程需要空间操作系统就会再分配,效率越来越低。

(3)便于管理

        物理内存中理论上可以在任意位置加载数据,那么是不是物理内存中的几乎所有数据和代码都是无序的呢?

         是的!!!

        相比于对物理内存中无序数据的管理,进程对有序数据的管理可以更加方便快捷,内存是否越界,越界多少都一目了然。

(4)独立性

6.再次理解进程状态

        前面我们提到过新建状态就是进程刚被创建的状态!刚被创建的时候进程是什么样的呢?

        程序加载的本质就是创建进程,那么是不是必须要立马把程序代码和数据加载到内存中,并创建内核数据结构建立映射关系呢?

        并不是!在最极端的情况下,甚至只有进程的内核数据结构被创建出来,只有所谓的task_struct,mm_struct等结构,这就是所谓的进程新建状态!

        并且,程序加载到内存中是分批次加载的,当然也可以分批次的换出,当一个进程数据短时间内不会被执行的时候,比如阻塞状态时,进程的代码和数据就会被换出形成挂起状态!

显示全文