深入理解C指针学习笔记(一)

第一章 认识指针

指针在C和C++中随处可见,它给程序员带来了极大的方便,指针为动态内存的分配提供了重要支持。使用指针我们可以方便的操控内存,提高程序的运行效率;同时利用指针变量我们可以实现各种数据结构,例如链表等;另一方面指针与数组的表示法密切相连,指向函数的指针也为程序中的流控制提供了更多的选择。
指针的基本概念和简单,就是一个存放内存地址的变量,所以理解指针的关键在于理解C程序如何管理内存。指针包含的是内存地址,不理解内存的组织和管理方式,就很难理解指针的工作方式。牢牢掌握了C语言程序的内存及其组织方式,理解指针就容易的多。

1、C中内存的组织和管理方式

1.1 C语言可执行程序的内存情况(预备知识)

首先Ubuntu环境下生成可执行文件a.out

gcc sample.c&&./a.out

然后使用Linux的size命令来查看可执行文件的内存分配和管理形式

可以看出,此执行程序在存储时分为代码段(text),数据段(data)和未初始化数据段(bss)(上述dec和hex分别是此执行文件所占的内存总大小的十进制和十六进制表示)

  • 代码段(text):存放CPU执行的机器指令。通常情况下,代码段是可以共享的,因为对于频繁被执行的程序,只需要在内存中有一份代码即可。代码段通常是只读的,使其只读的原因是防止程序意外的修改它的指令。另外,代码区还规划了局部变量的信息。
  • 数据段/全局初始化数据区和静态数据区(data):该区包含了程序中被初始化的全局变量,静态变量(全局静态变量和局部静态变量),静态函数和常数数据如字符串常量
  • 为初始化数据取(bss).此区域存储的是全局未初始化的变量。BSS区中的数据在程序开始执行前被内核初始化为0或空指针。例如
    int array[20000];
    char* myString;
    
    如上中的array和myString都是为初始化的全局变量,所以存储在bss区

1.2 C语言中正在执行的程序的内存管理与分配

一个正在运行的C程序代码区所占用的内存分为代码区,初始化全局变量或静态变量区,未初始化全局变量区,堆区和栈区五个部分。
其中五种内存的分配方式如图所示:

  • 代码区(text segment):代码区的指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。
  • 初始化全局变量或静态变量区(数据区):这里的数据只初始化一次。
  • 未初始化数据取(BBS):它的值在运行时改变
  • 栈区(stack):这部分的内存由编译器自动管理,内存由编译器分配,也由编译器释放。栈中存放的是函数的参数值,局部变量值等。它的操作方式类似于数据结构中的栈。每当一个函数被调用,该函数返回地址和一些关于被调用的信息,比如某些寄存器的内容,被存储到栈区,然后这个被调用的函数再为它的自动变量和临时变量在栈区上分配空间,这就是C实现函数递归调用的原理。每执行一次函数的递归调用,一个新的栈框架就会被使用,这样这个新实例栈里的变量就不会和该函数的另一个实例栈中的变量混淆。它的生长方式为高地址到低地址
  • 堆区(heap):堆是用来进行动态内存分配的内存区域。堆内存的位置处于BSS和栈区之间,一般由程序员分配和释放,若程序员忘记回收这部分内存,程序结束的时候会被操作系统自动收回。

程序在运行过程中分为多种内存类型的原因:
一个重要的原因是代码中不同的部分(例如代码和数据)的访问次数不同,将其放在不同的区域可以节省内存以及访问时间

  • 代码是根据流程依次执行的,只需要访问一次,当然跳转和递归有可能使代码执行多次,而数据一般要访问多次,因此单独开辟空间方便访问和节约空间。
  • 临时数据及需要再次使用的代码在运行的时候放到栈里面,生命周期较短。
  • 全局数据和静态数据有可能在整个程序执行的过程中都需要访问,因此单独存储管理。
  • 堆内存由用户自由分配和使用,放在一个特定的内存区域方便管理。

1.3堆和栈的区别

  • 管理方式不同
    栈由编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄露
  • 空间的内存大小不同
    栈是由高地址向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和最大容量是系统预先定好的,当申请的空间超过栈的剩余空间的时,将会提示栈溢出。因此用户能从栈获得的空间较小。
    堆是由低地址向高地址扩展的一种数据结构,是不连续的内存区域。因为操作系统是用链表来存储空闲地址的且链表的遍历方式是由低地址向高地址。由此可见,堆获得空间比较灵活也较大。
  • 是否产生碎片
    对于堆来讲,频繁的malloc/free势必会造成内存空间的不连续,从而产生大量的碎片,使程序效率降低对于栈来讲,不会出现这种问题。
  • 增长方向不同
    堆是从下向上增长的,即低地址向高地址增长。而栈是从上向下增长,即高地址向低地址。
  • 分配方式不同
  • 堆只能进行动态内存分配,通过malloc/free函数;但是栈既可以进行静态内存分配也能进行动态内存分配,静态内存分配是编译器进行自动分配和释放的,它利用alloca函数进行栈内存分配,但是不需要手动释放,编译器自动释放。
  • 分配效率不同
    栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。堆则是C函数库提供的,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大的空间,如果没有足够大的空间(可能是由于内存碎片太多),就有需要操作系统来重新整理内存空间,这样就有机会分到足够大小的内存,然后返回。显然,堆的效率比栈要低得多。

2、内存分配方式

内存分配包括静态分配和动态分配

  • 静态分配:静态分配是由编译器完成的,比如自动变量的分配。栈具有静态分配内存的方式,比如自动变量的分配。在程序执行之前完成,效率比较高。
    动态分配:动态分配是在程序执行的过程中动态的分配或者回收存储空间的内存分配方法。其中堆总是动态分配内存的,而栈也可以进行动态内存的分配(使用alloca函数,不提倡这么做)。在程序执行的过程中发生。

参考资料:《深入理解C指针》Richard Reese
博客:C语言中的内存分配

发布了131 篇原创文章 · 获赞 43 · 访问量 8万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览