一、区别

注:首先堆和栈可以分为两种,一种是数据结构,另一种是和内存的分配有关,这两种虽然都有栈和堆,但是两者关系并不大,

1、栈、堆是数据结构里面的叫法,注意:有时候有人喜欢这样说 “堆栈” 其实说的就是栈而不是堆。  

 2、堆区、栈区则是内存模型的叫法。

 

二、内存中的栈区和堆区

我们知道php的底层是C (任何语言其实都可以分为大同小异的几块)

而C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:

1、栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

2、堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。

3、静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。

4、常量区:常量存储在这里,不允许修改。

5、代码区:顾名思义,存放代码。

栈区和堆区大小差异?

栈区:由图中其实可以知道,栈区是向低地址扩展的,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,大小在进程分配时是确定的,具体大小看编译器,操作系统。所需大小一般小于10M!太大没有意义,不符合栈是用来快速存取的目标。

堆区:堆区是向高地址扩展的,是不连续的内存区域(这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的是动态分配的),因为会手动进行分配,会大一些,大小不固定。

栈区和堆区效率差异?

栈区:由系统自动分配,速度较快。但程序员是无法控制的。(只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。)

堆区:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。(首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中)

小结:其实从上面的知识我们可以看出,如果存放在堆中的数据如果不进行释放,很可能造成内存泄漏,因为并不一定能触发gc机制回收。所以对于堆中的内存使用,我们要记得用完释放。

原文链接:https://blog.csdn.net/panjiapengfly/article/details/102665381

三、举例

readelf -S example.elf 打印如下(gcc version 5.4.0):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
	   
	   ......
	   
  [14] .text             PROGBITS         0000000000400430  00000430
       00000000000001b2  0000000000000000  AX       0     0     16
  [15] .fini             PROGBITS         00000000004005e4  000005e4
       0000000000000009  0000000000000000  AX       0     0     4
  [16] .rodata           PROGBITS         00000000004005f0  000005f0
       0000000000000011  0000000000000000   A       0     0     4
  [17] .eh_frame_hdr     PROGBITS         0000000000400604  00000604
       000000000000003c  0000000000000000   A       0     0     4
  [18] .eh_frame         PROGBITS         0000000000400640  00000640
       0000000000000114  0000000000000000   A       0     0     8
  [19] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [22] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [23] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [24] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000028  0000000000000008  WA       0     0     8
  [25] .data             PROGBITS         0000000000601028  00001028
       0000000000000010  0000000000000000  WA       0     0     8
  [26] .bss              NOBITS           0000000000601038  00001038
       0000000000000008  0000000000000000  WA       0     0     1

可以看出来在内存中的地址从高到低依次是:

bss段:

BSS段(bsssegment)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。BSS是英文BlockStarted by Symbol的简称。BSS段属于静态内存分配。 特点是:可读写,在系统启动时BSS段会一般都会自动清0。bss类型的全局变量只占用 运行时的内存空间,而不占用可执行文件自身的文件空间

data段:

数据段(datasegment)通常是指用来存放程序中已初始化的全局变量和静态变量的一块内存区域。数据段属于静态内存分配。 如果data段里面数据全是零,为了优化考虑,编译器把它当作bss处理。通俗的说,data指那些初始化过(非零)的非const的全局变量。 data类型的全局变量是即占文件空间,又占用运行时内存空间的。

rodata段:

只读数据区, 比如字符串常量和const修饰的变量等. rodata的意义同样明显,ro代表read only,即只读数据(const)。关于rodata类型的数据,要注意以下几点:

  • 常量不一定就放在rodata里,有的立即数直接编码在指令里,存放在代码段(.text)中。
  • 对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份拷贝。
  • rodata是在多个进程间是共享的,这可以提高空间利用率。
  • 在有的嵌入式系统中,rodata放在ROM(如norflash)里,运行时直接读取ROM内存,无需要加载到RAM内存中。
  • 在嵌入式linux系统中,通过一种叫作XIP(就地执行)的技术,也可以直接读取,而无需要加载到RAM内存中。

由此可见,把在运行过程中不会改变的数据设为rodata类型的,是有很多好处的:在多个进程间共享,可以大大提高空间利用率,甚至不占用RAM空间。同时由于rodata在只读的内存页面(page)中,是受保护的,任何试图对它的修改都会被及时发现,这可以帮助提高程序的稳定性。

text段:

代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。

保留区(reserved)(32位cpu下占128M)

位于虚拟地址空间的最低部分,未赋予物理地址。任何对它的引用都是非法的,用于捕捉使用空指针和小整型值指针引用内存的异常情况。

  • 它并不是一个单一的内存区域,而是对地址空间中受到操作系统保护而禁止用户进程访问的地址区域的总称。
  • 在32位X86架构的Linux系统中,用户进程可执行程序一般从虚拟地址空间0x08048000开始加载。该加载地址由ELF文件头决定
  • 交换分区:在物理内存满时, 如果还需要内存资源,内核则把物理内存中非活动的页面放到交换分区中。

另外,bss的更高位的内存空间从高往低还有:

环境变量(environment variables)

命令行参数(command-line arguments)

栈(stack)

栈又称堆栈,由编译器自动分配释放,用来存储临时数据和栈帧。

内存映射段(memory mapping segment)

将硬盘文件的内容直接映射到内存,内存映射是一种方便高效的文件I/O方式, 因而被用于装载动态共享库。

堆(heap)

堆用于存放进程运行时动态分配的内存段。 与静态内存相比,使用堆内存的优势在于,可以在释放内存后重用内存,这是通过外部的deallocate调用实现的。

原文链接:https://blog.csdn.net/qq_41209741/article/details/99108928

全局的未初始化变量存在于.bss段中,具体体现为一个占位符;全局的已初始化变量存于.data段中;而函数内的自动变量都在栈上分配空间。.bss是不占用.elf文件空间的,其内容由操作系统初始化(清零);而.data却需要占用,其内容由程序初始化,因此造成了上述情况。

bss段(未手动初始化的数据)并不给该段的数据分配空间,只是记录数据所需空间的大小。 data(已手动初始化的数据)段则为数据分配空间,数据保存在目标文件中。 数据段包含经过初始化的全局变量以及它们的值。BSS段的大小从可执行文件中得到 ,然后链接器得到这个大小的内存块,紧跟在数据段后面。当这个内存区进入程序的地址空间后全部清零。包含数据段和BSS段的整个区段此时通常称为数据区。

code相关

static关键字用途太多,以致于让新手模糊。不过,总结起来就有两种作用,改变生命期和限制作用域。如:

  • 修饰inline函数:限制作用域
  • 修饰普通函数:限制作用域
  • 修饰局部变量:改变生命期
  • 修饰全局变量:限制作用域

const 关键字倒是比较明了,用const修饰的变量放在rodata里,字符串默认就是常量。对const,注意以下几点就行了。

  • 指针常量:指向的数据是常量。如 const char* p = “abc”; p指向的内容是常量 ,但p本身不是常量,你可以让p再指向”123”。
  • 常量指针:指针本身是常量。如:char* const p = “abc”; p本身就是常量,你不能让p再指向”123”。
  • 指针常量 + 常量指针:指针和指针指向的数据都是常量。const char* const p =”abc”; 两者都是常量,不能再修改。

violatile关键字通常用来修饰多线程共享的全局变量和IO内存。告诉编译器,不要把此类变量优化到寄存器中,每次都要老老实实的从内存中读取,因为它们随时都可能变化。这个关键字可能比较生僻,但千万不要忘了它,否则一个错误让你调试好几天也得不到一点线索。

原文链接:https://blog.csdn.net/jackjones_008/article/details/41978611 https://blog.csdn.net/linbounconstraint/article/details/38760673