C语言之程序在内存中的分布以及内存越界问题

C语言程序在内存中的分布:

   

bss段:该段用来存放没有被初始化或者初始化为0的全局变量,以及被static修饰的未初始化的局部变量。在程序运行的整个生命周期内都存在于内存中。这个段中的变量只占用程序运行时的内存空间,而不占用程序文件的储存空间。

  举个例子:定义一个1MB的未初始化的全局变量 (char类型只占一个字节 定义1024*1024个char类型说明有1MB)

#include <stdio.h>

char bss[1024*1024];

int main()
{ return 0;}

现在来看看程序的可执行文件大小

定义 char bss[1024*1024]={0};也是这个结果。

可以看到 bss的大小并没有1MB 说明未初始化的全局变量不占程序文件的存储空间。

data段(数据段):存放初始化过的全局变量和static修饰的初始化过的变量,程序运行的整个生命周期内都存在于内存中。这个段中的变量不仅占用程序运行时的内存空间,也占用程序文件的储存空间。

这里我们也举个例子:  定义一个1MB的初始化过的全局变量

#include<stdio.h>

char data[1024*1024]={1};

int main()
{return 0;}

运行结果:

可以看到可执行文件data的大小有1MB  因为数组分配的内存是连续的 所以就算只初始化了一个元素 后续内存依然会被开辟出来

rodata段(只读段):存储的是一些只能读取不能修改的数,一般是程序里面的只读变量(如const修饰的变量)和字符串字面量。ro就是Read Only的意思。

char* p = "12345"

     字符串"12345"可能在只读段,也有可能在代码段,看编译器而定。  

text段(代码段):程序的二进制数据,这段内存只能读,不能修改,这部分区域的大小在程序运行之前就已经确定。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。程序段为程序代码在内存中的映射,一个程序可以在内存中有多个副本。 

stack(栈区):保存函数的局部变量,参数以及返回值(但不包括static声明的变量)。是一种“后进先出”(Last In First Out,LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数据。对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO这种数据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间“向下减少”,即当栈上保存的数据越多,栈的地址就越低。   

heap(堆区):堆是用于存放进程运行中被动态分配的内存段,更准确的说是保存程序的动态变量。它完全由程序来负责内存的管理,包括什么时候申请,什么时候释放,而且对它的使用也没有什么大小的限制。

如何使用堆内存:

        C语言是没有操作内存的语句的,只能使用标准库提供的函数stdlib.h   使用堆内存要指针配置使用。
        内存申请函数  size表示要申请的字节数  void*返回的是申请到的内存的首地址。  

  void *malloc(size_t size);

       内存释放函数 ptr是要释放的内存的首地址 内存释放后ptr要及时的设置为空。 

 void free(void *ptr);

举个例子:

 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <unistd.h>
  
 int main()
 {
   printf("%d\n",getpid()); //得到进程id
   char* p = malloc(sizeof(char)*10);
   char str1[7] = "123avbd";
   char* str2 = "101";
   printf("&p:%p\np:%p\n",&p,p);
   printf("str1:%p\nstr2:%p\n",str1,str2);
   while(1){}; //死循环不让程序结束
   free(p);
   return 0;
 }

 

让我们看一下结果:

然后新开一个终端  输入 vim/proc/3149/maps 查看内存分配情况

可以看到 在堆区的只有p  而&p则是在栈区 

str1存放在栈区 

str2存放在只读区

注意:在堆区申请完内存使用完毕后一定要记得释放,即free,否则会造成内存泄漏   

 

其他的有关内存的操作函数:

1.申请适合数组使用的内存,size指的是一次申请多少个字节的内存,nmemb指的是申请多少次size,申请到的内存会被设置为0

 void *calloc(size_t nmemb, size_t size);

2.调整内存的大小,可以把ptr指向的内存,变大或变小。如果内存被调小,数据不会立即删除,会一直存在,直到被别人覆盖;如果内存调大,如果后面没有被使用,则在原来的基础上调大,如有人使用,会重新开辟一块内存,再把原来的数据复制过去。

  void *realloc(void *ptr, size_t size);

 

内存操作辅助函数:

        malloc函数申请到的内存内容是随机的以下函数可以把内存清理了。
        s为要清理的内存的首地址,n为内存的字节数   (头文件是strings.h)      

  void bzero(void *s, size_t n);

        s为要设置数据的内存的首地址,c为要设置的值(以字节为单位),n内存的字节数 (头文件是 string.h)

  void *memset(void *s, int c, size_t n);

 

碎片问题:对于堆来讲,频繁的malloc()势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低,也就是说当如果没有足够大小的空间,malloc()申请内存可能会失败。

 

虚拟内存:

        每个程序启动后,就有了0~4G的内存空间地址,但不能直接使用,因为它们是虚拟的。

        相当于操作系统给的一张空头支票,如果需要使用这些内存,需要让操作系统把这些内存与物理内存联系起来。   

内存映射:

        把虚拟内存与物理内存对应起来的过程叫内存映射,此时内存归malloc函数所有。
        当第一次向malloc申请内存时,操作系统会一次给程序映射33页内存(1页=4096byte)。  
           
内存分配:
        把内存使用权从malloc手里要过来,内存的释放也只是把内存使用权限交还给malloc。
        在使用malloc管理内存的过程,会有一些内存用来记录malloc的分配情况。
        
内存越界:
        1、超过映射的范围,会出现段错误。

        2、在映射范围内,会出现脏数据。

        3、当把malloc分配情况的信息修改了,会造成申请和释放内存的错误。

我们看个例子:

 #include<stdio.h>
 #include<string.h>
 
 int main()
 {
     char str[10]={};
     char arr[10]="0123456789";
     printf("str:%p\narr:%p\n",str,arr);
     puts(str); //打印str
     puts(arr); //打印arr
     strcpy(str,"abcdef123456");
     puts(str); 
     puts(arr);
     printf("len=%d\n",strlen(str));
     printf("size=%d\n",sizeof(str)/sizeof(str[0]));
     return 0;
}

这里定义了两个数组一个为空,一个初始化了,然后将字符串通过strcpy(字符串拷贝函数)赋给了str

此时这串字符串的长度已经大于str定义的长度10了,可是编译结果却没有报错 看下编译结果:

str甚至能够完整的输出   长度通过strlen(求字符串长度函数)来看也变成了12  但是通过sizeof可以看到str的长度还是10

 

而且这里arr的值却变成了56  这是为什么呢?

看下输出的前两行,打印的是两个数组的首地址 ,str的地址+10之后就是arr的首地址了,当使用strcpy函数时,发现str本身的大小不够拷贝了,于是它就会往后找,正好后面开辟了一段内存可以用,于是strcpy就把arr的内存给占用了,导致arr本来的数据被覆盖 ,这就是脏数据,也是所谓的内存越界的形式之一。

内存越界访问造成的后果非常严重,是程序稳定性的致命威胁之一。更麻烦的是,它造成的后果是随机的,表现出来的症状和时机也是随机的,让BUG的现象和本质看似没有什么联系,这给BUG的定位带来极大的困难。

对于内存越界,比较保险的方法还是在编程时就小心,特别是对于外部传入的参数要仔细检查(特别是外来的指针)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值