对c和c++中内存的一点总结,很多来自网上

内存四个区建立流程

1.操作系统把物理硬盘代码load到内存

2.操作系统把c代码分成四个区

3.操作系统找到main函数入口执行

 (heap)//malloc new free delete分配的内存在堆里,归操作系统管理,如果不手动释放的话不会消失,必须手动释放

 (stack)//函数中定义的数组,临时区由编译器自动分配释放,存放函数的参数,局部变量的值等

全局区/静态区(static):全局变量和静态变量的储存是放在一块的,初始化的全局变量和静态变量的储存是放在一块的,未初始化的全局变量和静态变量的储存是放在相邻的另一块的诺程序员不释放成员结束由操作系统释放、全局数组

常量区//字符串常量和其他常量的储存位置,程序结束后由操作系统释放

程序代码区//存放函数体的二进制

在定义了两个完全一样的全局变量,cc++编译器会优化将其分配同一个空间地址

char *p1=abc;

char *p2=abc;

 

函数1调用函数2,函数1是主调函数,函数2为被调用函数

规则1.Main函数(主调函数)分配的内存(在堆区,栈区,全局区)都可以被调用函数使用

规则2.在被调用函数里面分配的空间

1.如果在被调用函数里面的临时区(栈)分配内存,主调函数是不能使用的

2. 如果返回一个基本类型的变量,比如: 
int a; 
a = 5; 
return a; 
那么就会a的一个拷贝,即5返回,然后a就被销毁了。尽管a被销毁了,但它的副本5还是成功地返回了,所以这样做没有问题。 


3. 但是对于指针,像1那么做就会有问题,比如在某个函数内部: 
int a[] = {1, 2}; 
return a; 
那么也会返回指针a的一个拷贝,我们假定a的地址值为0x002345FC,那么这个0x2345FC是能够成功返回的。当return执行完成后,a
要被销毁,也就是0x002345FC所指向的内存被回收了。如果这时候在函数外面,去地址0x002345FC取值,那得到的结果肯定是不对的

char *getstring3()

   char buf[30];

strcpy(buf, "abcde");

return buf;

}//临时区在结束后自动释放

单线程程序有n个函数组成,c++编译器会建立一个堆一个栈

X不变时栈heap的开口向下,生长方向向下

int a;

int b;

printf("%d %d", &a,&b);

    2226284

226272

//堆栈的生长方向和数据的存储方向是不一样的

如数组char  buf[10];存储方向与生长方向相反

X不变时堆stack的开口向上,生长方向向下

printf("%d %d", malloc(10),malloc(20));

6193248

6189600

10//字面量 没有放在堆栈,全局区,可以当成放在代码区理解。不可取地址

register//CPU中的太监

首先,register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度不过,有些机器的寄存器也能存放浮点数。

  其次,因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址

  由于寄存器的数量有限,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。

  在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。

  早期的C编译程序不会把变量保存在寄存器中,除非你命令它这样做,这时register修饰符是C语言的一种很有价值的补充。然而,随着编译程序设计技术的进步,在决定那些变量应该被存到寄存器中时,现在的C编译环境能比程序员做出更好的决定。实际上,许多编译程序都会忽略register修饰符,因为尽管它完全合法,但它仅仅是暗示而不是命令

 

c++的编译器对register做了手脚

for(a=0;a<10000;a++)

c++编译器会自动将其放入register,不能对a取地址

而有些register int a; //c++编译器会自动优化,不将a放入register,所以有些也可以去地址

堆和栈

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。 
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

堆和栈的区别:

    1、管理方式不同;

       2、空间大小不同;

       3、能否产生碎片不同;

       4、生长方向不同;

       5、分配方式不同;

       6、分配效率不同;

       管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak

       空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:   

       打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit

注意:reserve最小值为4Bytecommit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。

       生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。

       分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由 alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。

       分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

       从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址, EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。


原文地址:https://www.cnblogs.com/wei1/p/9582139.html