c语言 变量的存储类别以及对应的内存分配?
<h4><strong>1.变量的存储类别</strong></h4>
从变量值存在的角度来分,可以分为静态存储方式和动态存储方式。所谓静态存储方式指在程序运行期间由系统分配固定的存储空间的方式(<strong>程序开始执行时分配,在程序完毕时释放,在程序过程中它们占据国定的存储单元,而不是动态分配和释放</strong>)。而动态存储方式在运行期间根据需要进行动态存储方式(<strong>在程序过程中申请和释放的一些空间</strong>)。内存中供用户使用的存储空间分为:程序区、静态存储区和动态存储区。程序放在程序区,数据放在静态存储区和动态存储区。在动态存储区存放函数形式参数、自动变量(未加static声明的局部变量)和函数调用时的现场保护和返回地址。
在C语言中每一个变量和函数两个属性:数据类型和数据的存储类别。存储方式分为两大类:静态存储和动态存储。具体保护:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。下面分别对上面关键字进行说明。
(1)auto:这个关键字是默认的,这个关键字属于动态存储区,声明时不进行默认初始化。
(2)static:如果希望局部变量调用结束后不消失保留此值(即加上改关键字在静态区分配空间存储),有默认初值。静态局部变量在编译时赋初值(即赋初值一次),以后每次运行保留上次的结果。如果是全局变量加上static时,就表示该变量的作用域只限于本模块。(全局变量默认存储在静态存储区)。同样,在多文件中为了防止名字冲突也可以将函数声明为static,这样只在文件内部引用
(3)register:一般情况下变量时存在内存中,程序用到变量时发出指令将内存中的该变量送到运算器中,经过运算,再存储到内种。这样如果频繁的使用某变量就将其定义为register类型(自由局部自动变量和函数形式参数),直接放在CPU中,提高效率。(一般的编译器为用户考虑这个问题,一般不需要考虑)
(4)extern:用来声明外部变量和外部函数(和static一种用法相对),extern只是声明而不是定义。两种情况:同一文件中声明extern(表示变量定义在当前引用的后面);多文件中声明外部变量(表示该变量定义在文件外部)。函数声明也是如此(一般情况函数声明都没有extern,省略的)。
<strong>注:类具有封装性,所以类中的成员均不能使用关键字extern、auto或register限定其存储类型。</strong>
<h4><strong>2.C语言内存分配机制</strong></h4>
(1)<strong>栈(Stack)</strong>:位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。
(2)<strong>堆(Heap)</strong>:由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收,但程序只要不结束,就有可能造成内存泄露。(程序员负责分配和释放)
(3)<strong>全局区/静态区(Global Static Area)</strong>: 全局变量和静态变量存放区,程序一经编译好,该区域便存在。并且在C语言中初始化的全局变量和静态变量和未初始化的放在相邻的两个区域(在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了)。由于全局变量一直占据内存空间且不易维护,推荐少用。程序结束时释放。
(4)<strong>C风格字符串常量存储区</strong>: 专门存放字符串常量的地方,程序结束时释放。
(5)<strong>程序代码区</strong>:存放程序二进制代码的区域。
<pre lang="c" line="1" escaped="true">实例1:
int a = 0; //全局初始化区
char *p1; //全局未初始化区(C++中则初始化为NULL)
int main()
{
int b; //b分配在栈上,整型
char s[] = "abc"; //s分配在栈上,char *类型;"abc\0"分配在栈上,运行时赋值,函数结束销毁
char *p2; //p2分配在栈上,未初始化
char *p3 = "123456"; //p3指向"123456"分配在字符串常量存储区的地址,编译时确定 p3存储在栈上
static int c = 0; //c在全局(静态)初始化区,可以多次跨函数调用而保持原值
p1 = (char *)malloc(10); //p1在全局未初始化区,指向分配得来得10字节的堆区地址
p2 = (char *)malloc(20); //p2指向分配得来得20字节的堆区地址
strcpy(p1, "123456"); //"123456"放在字符串常量存储区,编译器可能会将它与p3所指向的"123456"优化成一块
return 0;
}</pre>
<h4>3.C++语言内存分配机制</h4>
在C++语言中,与C类似,不过也有所不同,内存主要分为如下5个存储区:
(1)栈(Stack):位于函数内的局部变量(包括函数实参),由编译器负责分配释放,函数结束,栈变量失效。
(2)堆(Heap):这里与C不同的是,该堆是由new申请的内存,由delete或delete[]负责释放。
(3)自由存储区(Free Storage):由程序员用malloc/calloc/realloc分配,free释放。如果程序员忘记free了,则会造成内存泄露,程序结束时该片内存会由OS回收。
(4)全局区/静态区(Global Static Area): 全局变量和静态变量存放区,程序一经编译好,该区域便存在。在C++中,由于全局变量和静态变量编译器会给这些变量自动初始化赋值,所以没有区分了初始化变量和未初始化变量了。需要说明一点,全局静态变量和局部静态变量都是存储在同一个静态区(全局区),只是作用域不同。
(5)常量存储区: 这是一块比较特殊的存储区,专门存储不能修改的常量(一般是const修饰的变量,或是一些常量字符串)。
<h4>4.堆与栈的区别</h4>
(1)栈
具体的讲,现代计算机(冯诺依曼串行执行机制),都直接在代码低层支持栈的数据结构。这体现在,有专门的寄存器指向栈所在的地址(SS,堆栈段寄存器,存放堆栈段地址);有专门的机器指令完成数据入栈出栈的操作(汇编中有PUSH和POP指令)。
这种机制的特点是效率高,但支持数据的数据有限,一般是整数、指针、浮点数等系统直接支持的数据类型,并不直接支持其他的数据结构(可以自定义栈结构支持多种数据类型)。因为栈的这种特点,对栈的使用在程序中非常频繁的 。对子程序的调用就是直接利用栈完成的。机器的call指令里隐含了把返回地址入栈,然后跳转至子程序地址的操作,而子程序的ret指令则隐含从堆栈中弹出返回地址并跳转之的操作。
C/C++中的函数自动变量就是直接使用栈的例子,这也就是为什么当函数返回时,该函数的自动变量自动失效的原因,因而要避免返回栈内存和栈引用,以免内存泄露。
(2)堆
和栈不同的是,堆得数据结构并不是由系统(无论是机器硬件系统还是操作系统)支持的,而是由函数库提供的。基本malloc/calloc/realloc/free函数维护了一套内部的堆数据结构(在C++中则增加了new/delete维护)。
当程序用这些函数去获得新的内存空间时,这套函数首先试图从内部堆中寻找可用的内存空间(常见内存分配算法有:首次适应算法、循环首次适应算法、最佳适应算法和最差适应算法等)。如果没有可用的内存空间,则试图利用系统调用来动态增加程序数据段的内存大小,新分配得到的空间首先被组织进内部堆中去,然后再以适当的形式返回给调用者。当程序释放分配的内存空间时,这片内存空间被返回到内部堆结构中,可能会被适当的处理(比如空闲空间合并成更大的空闲空间),以更适合下一次内存分配申请。
这套复杂的分配机制实际上相当于一个内存分配的缓冲池(Cache),使用这套机制有如下原因:系统调用可能不支持任意大小的内存分配。有些系统的系统调用只支持固定大小及其倍数的内存请求(按页分配),这样的话对于大量的小内存分配来说会造成浪费;系统调用申请内存可能是代价昂贵的。 系统调用可能涉及到用户态和核心态的转换;没有管理的内存分配在大量复杂内存的分配释放操作下很容易造成内存碎片。
(3)栈和堆的对别
从以上介绍中,它们有如下区别:
a、栈是系统提供的功能,特点是快速高效,缺点是由限制,数据不灵活;而堆是函数库提供的功能,特点是灵活方便,数据适应面广,但是效率有一定降低。
b、栈是系统数据结构,对于进程/线程是唯一的;堆是函数库内部数据结构,不一定唯一。不同堆分配的内存无法互相操作。
c、栈空间分静态分配和动态分配,一般由编译器完成静态分配,自动释放,栈的动态分配是不被鼓励的;堆得分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配是良好程序的基本要素。
d、碎片问题:对于堆来讲,频繁的new/delete等操作势必会造成内存空间的不连续,从而造成大量的碎片,使程序的效率降低;对于栈来讲,则不会存在这个问题,因为栈是后进先出(LIFO)的队列。
e、生长方向:堆的生长方向是向上的,也就是向这内存地址增加的方向;对于栈来讲,生长方向却是向下的,是向着内存地址减少的方向增长。
f、分配方式:堆都是动态分配的,没有静态分配的堆;栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配则由alloca函数进行分配,但是栈的动态分配和堆不同,它的动态分配是由编译器进行释放,无需我们手工实现。
g、分配效率:栈是机器系统提供的数据结构,计算机在底层提供支持,分配有专门的堆栈段寄存器,入栈出栈有专门的机器指令,这些都决定了栈的高效率执行。而堆是由C/C++函数库提供的,机制比较复杂,有不同的分配算法,易产生内存碎片,需要对内存进行各种管理,效率比栈要低很多。
实例2:
看下面的一小段代码,体会堆与栈的区别:
<pre lang="c" line="1" escaped="true">
int foo()
{
//其余代码
int *p = new int[5];
//其余代码
return 0;
}
</pre>
其中的语句int *p = new int[5];就包含了堆与栈。其中new关键字分配了一块堆内存,而指针p本身所占得内存为栈内存(一般4个字节表示地址)。这句话的意思是在栈内存中存放了一个指向一块堆内存的指针p。在程序中先确定在堆中分配内存的大小,然后调用new关键字分配内存,最后返回这块内存首址,放入栈中。这段代码在VC6下的汇编代码为:
<blockquote>00401028 push 14h
0040102A call operator new(00401060)
0040102F add esp,4
00401032 mov dword ptr [ebp-8],eax
00401035 mov eax,dword ptr [ebp-8]
00401038 mov dword ptr [ebp-4],eax</blockquote>
如果需要释放内存,这里我们需要使用delete[] p,告诉编译器,我要删除的是一个数组。
<h4>5.一个非常经典的例子</h4>
实例3:
看下面的一小段代码,试着找出其中的错误:
<pre lang="c" line="1" escaped="true">
#include<iostream>
using namespace std;
int main()
{
chara[] ="hello";
a[0]= 'X';
cout<< a<< endl;
char*p ="world";
p[0]= 'X';
cout<< p<< endl;
return0;
}</pre>
发现问题了吗?是的,字符数组a的容量是6个字符,其内容为"hello\0"。a的内容时可以改变的,比如a[0]='X',因为其是在栈上分配的,也就是在运行时确定的内容。但是指针p指向的字符串"world"分配在字符串常量存储区,内容为"world\0",常量字符串的内容时不可以修改的。从语法上来说,编译器并不觉得语句p[0]='X'有什么问题,但是在运行时则会出现"accessviolation"非法内存访问的问题。
c语言 变量的存储类别以及对应的内存分配?的更多相关文章
- C语言变量的存储类别
我们知道,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量. 从另一个角度,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式. 静态存储方式:是指在程序运行期 ...
- C语言中存储类别、链接与内存管理
第12章 存储类别.链接和内存管理 通过内存管理系统指定变量的作用域和生命周期,实现对程序的控制.合理使用内存是程序设计的一个要点. 12.1 存储类别 C提供了多种不同的模型和存储类别,在内存中 ...
- C++变量的存储类别与作用域
总结一下C++中变量的存储类别以及变量的作用域. (1)标示符的存储类别决定了标示符在内存中存在的时间(我们可以理解标示符就是确定一个变量的符号,也就是我们所说的变量名) 二:存储类别 (1)静态存储 ...
- [C++程序设计]变量的存储类别
全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储单元,程序执行完毕就释放这些空间.在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放. 在动态存储区中存放以下数据: ...
- c++ 变量的存储类别
c++的存储类别 首先我们得知道c++的变量存储方式:静态存储和动态存储两种,全局变量使用的是静态存储,函数的形参和局部变量是使用的动态存储. 当然在有的教程中又分为自动存储,静态存储,动态存储.相信 ...
- C语言变量的存储布局
分析以下代码中变量存储空间如何分配: //MemSeg.c: 代码无意义,仅供分析用 #include <stdio.h> #include <stdlib.h> //mall ...
- C Primer Plus学习笔记(十一)- 存储类别、链接和内存管理
存储类别 从硬件方面来看,被储存的每个值都占用一定的物理内存,C 语言把这样的一块内存称为对象(object) 对象可以储存一个或多个值.一个对象可能并未储存实际的值,但是它在储存适当的值时一定具有相 ...
- 你的变量究竟存储在什么地方 && 全局内存
我相信大家都有过这样的经历,在面试过程中,考官通常会给你一道题目,然后问你某个变量存储在什么地方,在内存中是如何存储的等等一系列问题.不仅仅是在面试中,学校里面的考试也会碰到同样的问题. 如果你还不 ...
- 【C语言天天练(二四)】内存分配
引言: 对于C语言程序,了解它执行时在内存中是怎样分配的对于我们理解它的执行机制是很实用的.以下就总结一下C语言程序的一些内存分配知识. 一 一段C程序.编译连接后形成的可运行文件一般有代码段.数据段 ...
随机推荐
- AngularJS Injector和Service的工作机制
要了解angularJS里的injector和Service是如何工作的,需要阅读/src/auto/injector.js.另外要结合/src/loader.js才能明白它的应用场景. auto/i ...
- Eclipse安卓项目导入android.support.design报错的解决办法
导入android.support.design出错:1.项目除了需要依赖appcompat_v7包外还要design包2.design包就是在安卓sdk下Extras中的android.suppor ...
- ExtJs4.2.1中的Ext.grid.GridPanel选择行回车事件
网上大多说的是“rowdblclick” 其实是“itemdblclick” 这个东西坑了我一上午.
- (局部刷新)jquery.ajax提交并实现单个div刷新
web开发中我们经常会遇到局部刷新页面的需求,以前我经常使用ajax和iframe实现局部刷新,后来做政府的项目,对页面的样式要求比较多,发现使用iframe控制样式什么的很麻烦,所以就采用了新的办法 ...
- TIME_WAIT和CLOSE_WAIT
先看下三次握手四次挥手的状态变化: 通常会遇到下面两种情况: 服务器保持了大量TIME_WAIT状态 服务器保持了大量CLOSE_WAIT状态 因为linux分配给一个用户的文件句柄是有限的,而TIM ...
- cocos2d3.0rc编译android工程
1. 在CMakeLists.txt中配置所有的cpp文件 2. 在proj.android/jni 下的Android.mk中配置所有的cpp文件 3.新建工程 cocos new mygame1 ...
- npm install遇到的问题
phantomjs-prebuilt@2.1.16 install: 'node install.js' 在虚拟机上初始化vue-cli项目,npm install时遇到的问题 npm install ...
- Matlab 一些函数
max(A,[],dim):dim取1或2.dim取1时,该函数和max(A)完全相同:dim取2时,该函数返回一个列向量,其第i个元素是A矩阵的第i行上的最大值.
- canvas之图形的变化(平移,缩放,旋转)
1.保存与恢复canvas状态 ctx.save();暂时将当前的状态保存到堆中 ctx.restore();该方法用于将上一个保存的状态从堆中再次取出,恢复该状态的所有设置. <!DOCTYP ...
- 使用XMLHttpRequest对象完成原生的AJAX请求
1.大家眼中的Ajax 说到Ajax,只要有过前端开发经验的童鞋一定都不陌生,大都知道它就是一种与后端之间的通信技术,通过这个神奇的家伙,我们不用像传统表单那样填完信息一点提交就呼啦呼啦跳转了.Aja ...