算法与数据结构前置知识(3)—内存

2020-01-31  本文已影响0人  程序员班吉

1. 机算机的内存逻辑

要搞明白c语言内存是怎么回事,就要先搞清楚计算机内存基本逻辑。我们不讲深奥的计算机原理,我们来看一下运行在计算机上的操作系统是怎么管理内存的。

操作系统将内存划分为几个段(不同的操作系统可能有些差异),通常情况下分为栈、堆、全局量、常量、代码空间,如下图:


图:1-1

在linux系统中,内核通过代码将物理内存虚拟出来一层,中间通过内核的数据结构对内存进行转换,这就是我们通常说的虚拟内存,如下图:


图:1-2

上图只是大概描述了虚拟内存和物理内存之间的关系,操作系统实际实现要比这复杂得多。在linux内核以及redis、php的zend引擎等对内存的管理都是都是其最核心也是最复杂的内容之一,其中的源码也是精华所在,例如redis用于管理集合的跳跃表,其核心思想就是链表这一类基础的数据结构。所以,操作系统也好,各类优秀的开源软件也好,究其本质都是数据结构+算法,这也是为什么算法和数据结构很重要的原因。

下面我们来看一段代码:

int dev = 10; 
const int test = 20; 
void get_config(char *path) 
{ 
  int flag; 
  int *px = NULL; 
  px = malloc(sizeof(int)); 
  *px = 100; 
}
我们来分析一下:

此时,这段代码的内存分布情况如下图:


图:1-3

各个内存区的生命周期如下:
栈:栈空间会随着函数结束而回收,生命周期限于函数内。
堆:贯穿整个程序的生命周期,后面的数据结构也是操作的堆空间。
全局量:贯穿整个程序的生命周期,全局可用。
常量:限所在代码片的生命周期。如果在函数内部那生命周期就是函数的生命周期。
当然,真实的物理地址并不是像上图一样去存储数据的,而是将代码经过编译器转化成汇编,汇编转换成机器码,运行程序的时候以高低电位的形式保存在真实的物理内存中。这部分深挖下去的话跨不过计算机组成原理这道坎,这是一块复杂又庞大的内容,这里不再深入。

2. c语言的动态内存分配

2.1. malloc和free

void *malloc(size_t size);

malloc逻辑如下:

void free(void *p)

free逻辑如下:

2.2. calloc和realloc

void *calloc(size_t num_element, size_t element_size)

calloc的逻辑如下:

realloc的逻辑如下:

2.3. 使用动态内存要注意的坑

2.3.1. 分配的内存没有free

由于使用malloc和calloc分配的内存是在堆区,对于整个程序是全局的,在使用的时候要特别小心,对于分配的内存一定要有对应的释放操作。

while(1) {
   int *p = malloc(sizeof(int));
   ... 
}

上面我们在一个循环里使用了malloc分配了一块大小为int的内存地址,但是我们没有使用free来释放。这样实际会造成内存溢出造成没有内存可用最终导致最严重的系统崩溃。

下面我们再看一段代码

int build_data_from_file() 
{ 
  int *p_m; FILE *fp; 
  fp = fopen("test.json", "w"); 
  p_m = malloc(sizeof(int)); 
  if (fp == -1) { 
    return 0; 
  } 
  .... 
  free(p_m); 
  return 1; 
}

上面我们看到如果fp==-1的情况下,内存得不到释放,同样也会导致内存溢出。

2.3.2. 内存释放后被使用

int *p_t; 
p_t = malloc(sizeof(int)); 
free(p_t); 
*p_t = 100;

上面我们申请了一块内存,紧接着又释放了内存,然后我们又将分配到的指针的位置赋值为100。由于在赋值之前已经使用free释放了内存,这个时候p_t是一个NULL在许多编译器是编译不通过的。

2.3.3. 越界访问

int *p_a; 
p_t = malloc(20*sizeof(int)); 
int arr[20] = p_t; 
arr[22] = 200;

上面我们分配了一个大小为20元素为int的指针,当我们使用arr[22]的时候已经超出了p_a指针所指向的最大指针,这个时候arr[22]指向了哪里我们是不知道的,这类bug也是最难排查的,控制这类问题一个比较好的方法是在操作之前严格判断长度。

动态内存分配还有其它很多异常,比如释放了一个不是malloc分配的内存地址,再比如释放了部分内存等等。以上是经常出现的几个高频bug,相信能在编程的时候考虑到这些问题应该能对提高代码的健壮性有很大的帮助。

上一篇 下一篇

猜你喜欢

热点阅读