声明变量一个变量,有没有对它进行内存空间分配和初始化

当前位置: >>
C变量的作用域,和存储空间分配
C 变量的作用域,和存储空间分配1. 2. 3. 4. 5.C 变量的作用域 变量修饰符 动态分配内存 C 变量的存储空间实现机制 字节对齐.一. 概述C 变量的作用域 C 的变量作用域是指一个变量在一个程序中能被有效访问范围.按范围来划分,分为变 量的作用域分为局部、全局、文件三种范围. 变量的作用域都是通过它在程序中的位置隐式说明的。 因此开发者必须对变量的作用域有 所了解. 变量的存储 变量是对程序中数据的存储空间的抽象,变量存储包括两种情况,一种是被链接器链接成 可执行程序后,变量在可执行程序文件里所占的空间. 一种是运行后变量在内存所占空间的分布.两种情况要分开讨论.前者我们称为存储态, 后者称为运行态.两者有共同点也有区别,在后面将分开讨论. C 变量的作用域属于 C 语言的语法问题,所有标准 C 程序都遵循相同规则.如果错误的使 用常造成无法编译通过. 但是讨论变量存储不能脱离 CPU,操作系统和编译器的类型来谈,特别对需要经常在不同 操作系统下编程的开发者.但是此类问题有一定共性.为方便讨论,后文均指 32 位 CPU 和 Windows 操作系统. 对变量的存储理解不清楚,最常犯的错识就是对不能修改的字符串区进行修改造成程序崩 溃.变量的作用域 二. 变量的作用域C 的变量作用域是指一个变量在一个程序中能被有效访问范围.按范围来划分,分为变 量的作用域分为局部、全局、文件三种范围. 1.局部作用域 声明在函数内部的变量都是局部作用域, 无法被其他函数的代码所访问。 函数的形式参 数的作用域也是局部的, 它们的作用范围仅限于函数内部所用的语句块。 还有一种特殊是函 数中的用{}包括语句块定义变量也是局部作用域,而且只在语法块里有效.void add(int);main() { int num=5; add(num); printf(&%d\n&,num); } /*输出 5*/void add(int num) { num++; { int num =1; printf(&%d\n&,num); /*输出 1*/ } printf(&%d\n&,num); } /*输出 6*/上面例子里的三个 num 变量都是局部变量,只在本身函数里可见。,在两个函数出现同名的 变量不会互相干扰。但同一函数定义同名变量,最内层会覆盖外层同名变量(上例中红色 num 会覆盖蓝色 num,在执行完语法块后,红色 num 失效,程序对 num 的引用又变成了对函数参数 的引用).所以上面的两个输出,在主函数里仍然是 5,在 add()函数里输出是 1,6。 2.全局作用域 对于具有全局作用域的变量, 我们可以在程序的任何位置访问它们。 当一个变量是在所 有函数的外部声明,那么这个变量就是全局变量。 void add(int); int num=3;main() { int n=5; add(n); printf(&%d\n&,num); } /*输出 3*/void add(int num) num++; printf(&%d\n&,num); }{/*输出 6*/add()里的 num 修改是局域变量.对 main 中的全局变量 num 不影响.因此输出是 6,3 6. 文件作用域 文件作用域是指外部标识符仅在声明它的同一个转换单元内的函数汇总可见。 所谓转换 单元是指定义这些变量和函数的源代码文件(包括任何通过#include 指令包含的源代码文 件)。static 存储类型修饰符指定了变量具有文件作用域。 换句话说,在一个源码文件里的用 static 修饰的变量和函数,只能被同一个源码文件里的 函数所引用/调用.这在没有 private 和 protected 关键字的 C 语言里,用 static 是实现数据封装, 防止被外部程序改动的一种主要手段,大量在程序中被采用. static void add(int);main() { scanf(&%d&,&num); add(num) printf(&%d\n&,num); }void add(num) { num++; }上面的程序中变量 num 和函数 add()在声明是采用了 static 存储类型修饰符,这使得它 们具有文件作用域,仅在定义它们的文件内可见。 局域变量是指定义:在函数内定义,只在本函数内有效 main 中定义的变量只在 main 中有效 不同函数中同名变量,占不同内存单元 形参属于局部变量 可定义在复合语句中有效的变量 局部变量可用存储类型:auto register static (默认为 auto) 全局变量---外部变量: 在函数外定义,可为本文件所有函数共用 有效范围:从定义变量的位置开始到本源文件结束,及有 extern 说明的其它源 文件 外部变量声明: extern 数据类型 变量表; 外部变量定义与外部变量说明不同, 定义是产生实际效果的语句,在整个项目里,同名的全局变量只能定义一 次,外部变量声明只是说明全局变量的类型和名字,可以出现多次. 外部变量可用存储类型:缺省 或 static扩 展 后 后 展扩c1,c2 c1,c2 的 的 作 作 用 用 范 范 围 围int p=1,q=5; extern char c1,c2; float f1(int a) { int b,c; extern char c1,c2; ……. } int f3() {….. } char c1,c2; char f2(int x,int y) { int i,j; …… } main() { int m,n; ……. }p,q的作用范围c1,c2的作用范围 例 外部变量定义与说明 Ch7_16.c int max(int x, int y) { z=x&y?x:y; return(z); } main() { extern int a,b; printf(&max=%d&,max(a,b)); } int a=13,b=-8; 运行结果:max=13 extern int a,b; int max() { z=a&b?a:b; return(z); } main() { printf(&max=%d&,max()); } int a=13,b=-8;上面两个例子运行结果都是一样,只是显示如何通过 extern 来扩大全局的使用范围. 另外一个重要一点是.extern 只是声明 不产生实际空间 因此对其声明变量赋初值是有 只是声明,不产生实际空间 不产生实际空间,因此对其声明变量赋初值是有 编译错误的. 编译错误的 extern int a=1; /* 错误 编译器会产生编译错误 */ 错误,编译器会产生编译错误 按软件工程学的观念,全局应该少用,因为他会带来许多问题 全局变量在程序全部执行过程中占用存储单元 降低了函数的通用性、可靠性,可移植性 降低程序清晰性,容易出错变量的存储修饰符 三. 变量的存储修饰符各种变量的作用域不同,本质上是因为变量存储类型不同.主要是指运行态时存储占用内 存空间的方式.变量的存储方式可分为”静态存储”和”动态存储”两种. 静态存储变量通常是指在变量定义时就分布存储单元并一直保持不变.直至整个程序结 束.比如上面所说的全局变量. 动态存变量是在程序执行过程中,使用时才分配存储单元,使用完毕立即释放.典型的例 子是函数的形式参数,在函数定义时并不给形参分配存储单元,只是函数被调用时才以分配, 调用函数完毕立即释放.如果一个函数被多次调用,则反复地分配,释放形参变量的存储单元. 静态存储变量是在程序运行时是一直存在的,而动态存储变量则时而存在时面消失.这种 由于存储方式产生不同而产生存在时间不同的特性称为变量的生存期.生存期表示变量存在 的时间范围. 生存期和作用域是从时间和空间这两个不同角度来描述变量的特性,这两者即有联系,又 有区别.一个变量究竟属于哪一种存储方式,并不能仅仅从它的作用域来判断,还应有明确的 存储类型声明.在 C 语言,对变量的存储类型声明有以下 4 种 自动(auto)、 静态(static)、 外部(extern)、 寄存器(regiser)。 其中 auto,register 属于动态存储方式,extern 变量和 static 变量属于静态存储试式. 1.自动存储类型(auto) 自动存储类型修饰符指定了一个局部变量为自动的, 这意味着, 每次执行到定义该变量 的语句块时,都将会为该变量在内存中产生一个新的拷贝,并对其进行初始化。实际上,如 果不特别指明,局部变量的存储类型就默认为自动的,因此,加不加 auto 都可以。 main() { auto int num=5; /* 等于 int num=5;*/ printf(&%d\n&,num); } 在这个例子中,不论变量 num 的声明是否包含关键字 auto,代码的执行效果都是一样 的。函数的形式参数存储类型默认也是自动的。 2.静态存储变量(static) 前面已经使用了 static 关键字,但是对于局部变量,静态存储类型的意义是不一样的, 这时, 它是和自动存储类型相对而言的。 静态局部变量的作用域仍然近局限于声明它的语句 块中,但是在语句块执行期间,变量将始终保持它的值。而且,初始化值只在语句块第一次 执行是起作用。在随后的运行过程中,变量将保持语句块上一次执行时的值。 /*1.C*/ int add(); main() { result=add() printf(&%d &,result); result=add(); printf(&%d &,result); result=add(); printf(&%d&,result); } int add() { int num=50; num++; }/*2.C*/ int add(); main() { result=add(); printf(&%d &,result); result=add(); printf(&%d &,result); result=add(); printf(&%d&,result); } int add() { static int num=50; num++; }上面两个源文件,只有函数 add()里的变量声明有所不同,一个是自动存储类型,一个 是静态存储类型。 对于 1.C 文件,输出结果为 51 51 51;这很好理解,每次初始值都是 50,然后加 1 上来。 对于 2.C 文件,输出结果为 51 52 53;这是由于变量是静态的,只在第一次初始化了 50, 以后都是使用上次的结果值。当第一次调用 add()时,初始化为 50,然后加 1,输出为 51; 当第二次调用时,就不初始化了,这时 num 的值为上次的 51,然后加 1,输出 52;当第三 次调用时,num 为 52,加 1 就是 53 了。 标识符就构成静态局域变量.静态局域变量有如下特点, 在局部变量的定义前加上 static 标识符就构成静态局域变量 它有如下特点: 静态局部变量在函数内定义,但不像自动变量一样,当调用时就存在,退出函数时就消 失. 静态局部变量始终存在,也就是说它的生存期为整个源程序 静态局部变量的生存期虽然为整个源程序,但是其作用域与自动变量相同,即可能在 定义该变量的的函数内使用该变量.退出该函数后,尽管该变量还继续存在,但不能使 用它 允许对静态局域变量赋初值,若未赋初值,则系统自动赋以 0 值 标识符就构成了静态全局变量.他与非静态的全局变量的最大 全局变量 在全局变量定义前加上 static 标识符就构成了静态全局变量 区别是静态全局变量作用域是当前源程序.而全局变量作用域是整个源程序 3.外部存储类型(extern) 外部存储类型声明了程序将要用到的、但尚未定义的外部变量。通常,外部存储类型都 是用于声明在另一个转换单元中定义的变量。这在前一节已经解释过. 外部变量的声明既可以在引用它的函数的内部, 也可以在外部。 如果变量声明在函数外 部,那么同一转换单元内的所有函数都可以使用这个外部变量。反之,如果在函数内部,那 么只有这一个函数可以使用该变量。 /*1.C*/ extern void a(); main() { a(); printf(&%d\n&,num); } /*2.C*/ void a() { num=5; }4.寄存器存储类型(register) 被声明为寄存器存储类型的变量, 除了程序无法得到其地址外, 其余都和自动变量一样。 使用寄存器存储类型的目的是让程序员指定某个局部变量存放在计算机的某个硬件寄 存器里而不是内存中,以提高程序的运行速度。不过,这只是反映了程序员的主观意愿,编 译器可以忽略寄存器存储类型修饰符。 寄存器变量的地址是无法取得的,因为绝大多数计算机的硬件寄存器都不占用内存地址。 而且, 即使编译器忽略寄存器类型修饰符把变量放在可设定地址的内存中, 我们也无法取地 址的限制仍然存在。main() { num=100; printf(&%d&,num); }关于寄存器变量的说明: 只有局部自动变量和形式参数才可以定义为寄存器变量,因为寄存器变量属于动态 存储方式.凡需要采用静态存储方式的的变量不能定义为寄存器变量 在某一些 C 编译器中,如 Turbo C,MS C 中,实际上把寄存器变量当成自动变量处理, 因此程序的运行速度并不能提高,在程序中允许使用寄存器变量只是为了与标准 C 保持一致 即使能真正使用寄存器变量的机器,由于 CPU 的寄存器的个数是有限的,因此使用 寄存器变量的个数也是有限的.并不是程序使用 register ,编译器就一定会把它编译 成寄存器变量. 在现代的 C 编译器,优化已经做得相当出色了,需要优化地方基本上会自动优化.纯 粹为了提高运行速度.用 register 显得多余. 四. 动态内存分配在使用数组的时候,总有一个问题困扰着我们:数组应该有多大? 在很多的情况下, 你并不能确定要使用多大的数组.即便是你的程序在运行时就申请 了固定大小的你认为足够大的内存空间。但是如果因为某种特殊原因人数有增加或者减 少,你又必须重新去修改程序,扩大数组的存储范围。这种分配固定大小的内存分配方 法称之为静态内存分配。 但是这种内存分配的方法存在比较严重的缺陷,特别是处理某些问题时:在大多数 情况下会浪费大量的内存空间,在少数情况下,当你定义的数组不够大时,可能引起下 标越界错误,甚至导致严重后果。 那么有没有其它的方法来解决这样的外呢体呢?有,那就是动态内存分配。 所谓动态内存分配就是指在程序执行的过程中动态地分配或者回收存储空间的分 配内存的方法。动态内存分配不象数组等静态内存分配方法那样需要预先分配存储空 间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小。 从以上动、静态内存分配比较可以知道动态内存分配相对于静态内存分配的特点: 不需要预先分配存储空间; 分配的空间可以根据程序的需要扩大或缩小。 如何实现动态内存分配及其管理 要实现根据程序的需要动态分配存储空间,就必须用到以下几个函数 1、malloc 函数malloc(配置内存空间) 相关函数 calloc,free,realloc,brk 表头文件 #include&stdlib.h& 定义函数 void * malloc(size_t size); 函数说明 malloc()用来配置内存空间,其大小由指定的 size 决定。 返回值 若配置成功则返回一指针,失败则返回 NULL。 范例 void *p = malloc(1024); /*配置 1k 的内存*/ 其作用是在内存的动态存储区中分配一个长度为 size 的连续空间。 其参数是一个无符号 整形数, 返回值是一个指向所分配的连续存储域的起始地址的指针。 还有一点必须注意的是, 当函数未能成功分配存储空间(如内存不足)就会返回一个 NULL 指针。所以在调用该函 数时应该检测返回值是否为 NULL 并执行相应的操作。 下例是一个动态分配的程序:上 例 中 动 态 分 配 了 10 个 整 型 存 储 区 域 , 然 后 进 行 赋 值 并 打 印 。 例 中 #include &stdlib.h& main() { int count,* /*count 是一个计数器,array 是一个整型指针,也可 以理解为指向一个整型数组的首地址*/ if((array=(int *) malloc(10*sizeof(int)))==NULL) { printf(&不能成功分配存储空间。&); exit(1); } for (count=0;count〈10;count++) /*给数组赋值*/ array[count]= for(count=0;count〈10;count++) /*打印数组元素*/ printf(&%2d&,array[count]); free(array); }if((array=(int *) malloc(10*sizeof(int)))==NULL)语句可以分为以下几步: 1)分配 10 个整型的连续存储空间,并返回一个指向其起始地址的整型指针 2)把此整型指针地址赋给 array 3)检测返回值是否为 NULL 2、free 函数free(释放原先配置的内存) 相关函数 malloc,calloc,realloc,brk 表头文件 #include&stdlib.h& 定义函数 void free(void *ptr); 函数说明 参数 ptr 为指向先前由 malloc()、calloc()或 realloc()所返回的内存指针。调 用 free()后 ptr 所指的内存空间便会被收回。 假若参数 ptr 所指的内存空间已被 收回或是未知的内存地址,则调用 free()可能会有无法预期的情况发生。若参数 ptr 为 NULL,则 free()不会 有任何作用。由于内存区域总是有限的,不能不限制地分配下去,而且一个程序要尽量节省资源,所 以当所分配的内存区域不用时,就要释放它,以便其它的变量或者程序使用。这时我们就要 用到 free 函数。 其函数原型是 void free(void *p) 作用是释放指针 p 所指向的内存区。 其参数 p 必须是先前调用 malloc 函数或 calloc 函数(另一个动态分配存储区域的函数) 时返回的指针。给 free 函数传递其它的值很可能造成死机或其它灾难性的后果。 注意:这里重要的是指针的值,而不是用来申请动态内存的指针本身。例:int *p1,*p2; p1=malloc(10*sizeof(int)); p2=p1; …… free(p2) /*或者 free(p1),如果连续释放 free(p1); free(p2); 第二次释放造成程序崩溃 */malloc 返回值赋给 p1,又把 p1 的值赋给 p2,所以此时 p1,p2 都可作为 free 函数的参 数。 /* 内存泄漏 1: */ int main() { char * p= malloc(1024); p= malloc(512); free(p); } malloc 函数是对存储区域进行分配的。 函数是对存储区域进行分配的。 free 函数是释放已经不用的内存区域的。 函数是释放已经不用的内存区域的。 所以由这两个函数就可以实现对内存区域进行动态分配并进行简单的管理了。 注意静态分布内存和静态变量是两个领域的划分,如果说出两种联系,可以认为目前变量 定义全部是采用静态分配空间.而只有采用 malloc 分配空间才能叫动态分配内存,如下列方式 都是静态分配内存 main(){ int array[10]; a=5;b=6;array[0]=10; printf(“%d,%d,%d\n”,a,b,array[0]); } 下列用 malloc 动态分配空间,才叫动态分配内存. void func(){ int *a; static int* int * a = malloc(sizeof(int)); b= malloc(sizeof(int)); array = malloc(sizeof(int)*10); *a=5;*b=6;array[0]=10; printf(“%d,%d,%d\n”,*a,*b,array[0]); free(a);free(b);free(array); 从实现机制来说,malloc 是从程序的堆空间(heap)选择一块空间给用户.五. 变量的存储方式实现机制变量是对程序中数据的存储空间的抽象,变量存储包括两种情况,一种是被链接器链 接成可执行程序后,变量在可执行程序文件里所占的空间. 一种是运行后变量在内存所占空间的分布.两种情况要分开讨论.前者我们称为存储态, 后者称为运行态.两者有共同点也有区别,在后面将分开讨论. 不同的操作系统的可执行程序的格式不一样,如 Windows 的可执行程序移为 PE 格式,而 Linux 的可执行行格式通常为 ELF 格式,虽然有不同格式,但是其实现原理和总体结构类似. 对于一个 C 的程序而言,在存储态,即程序还是一个可执行文件时.通常包含如下几个段 Text 段 Data 段 BSS 段(Block Storage Start). 如果包含字符串常量,还带的 rodata 段(在 Windows PE 下叫 rdata,而 gcc 编译出来叫 rodata 段) 不同的变量分布在哪一个段里,是一个 C 开发者必备知识之一.存储态的分段编译器会把源代码各部分按如下规则把各部分放到可执行程序文件各个段中 代码段(text segment):存放 CPU 执行的机器指令(machine instructions)。也就是存储 你的程序代码编译后的机器代码,在内存中,这一段是只读的.所有可执行代码即 C 的 所有可执行代码即 语句和函数都会被编译到可执行段中 初始化数据段/数据段(initialized data segment/data segmen,包含静态初始化的数据, 所 以有初值的全局变量和 static 变量在 data 区。 未初始化数据段/bss 段,,bss 是英文 Block Started by Symbol 的简称, 通常是指用来存 放程序中未初始化的全局变量和静态变量的一块内存区域,在程序载入时由内核清 , 0,所有未初始化的全局变量 包括括静态或非静态的 均保存在这一个段 所有未初始化的全局变量,包括括静态或非静态的 均保存在这一个段. 所有未初始化的全局变量 包括括静态或非静态的,均保存在这一个段 因为没有初始化数据,,为了节约可执行文件空间,所以所有在 BSS 段里的程 序只占一个标识符空间,和记录所占空间大小,其变量所占空间没有展开, 所有在 BSS 段里变量在装入时由操作系统统一清 0 rodata/rdata 段, ro 代表 read only,即只读数据(const)。表示只读的数据,比如字 比如字 符串文字量、常量(常量有时可以直接保存在 符串文字量、常量 常量有时可以直接保存在 Text); 注意在可执行文件是不会有局域变量的空间的,因为他们要等于执行后在进程的空 注意在可执行文件是不会有局域变量的空间的 因为他们要等于执行后在进程的空 间动态创建. 间动态创建#include &stdio.h& /* 未初始,分配到 bss 段 */ static int b=3; /* 已初始化变量,分配到 data 段中*/ int c=4; /*已初始化变量,分配到 data 段中*/ static char d[10]={3,4};/*已初始始化变量,分配到 data 段中,其中数据直接写在 data 空间 */ main() { int e=3; printf(“hello !”); printf(“%d,%d,%d,%s,%d”,a,b,c,d,e); }运行态的分段当程序运行后.除了上述段会装入进程空间,还有两个新的区分配在进程空间中. 栈段(stack), 保存函数的局部变量和函数参数 是一种“后进先出” 保存函数的局部变量和函数参数。 (Last In First Out, LIFO)的数据结构,这意味着最后放到栈上的数据,将会是第一个从栈上移走的数 据。 对于哪些暂时存贮的信息,和不需要长时间保存的信息来说,LIFO 这种数 据结构非常理想。在调用函数或过程后,系统通常会清除栈上保存的局部变 量、函数调用信息及其它的信息。栈另外一个重要的特征是,它的地址空间 “向下减少”,即当栈上保存的数据越多,栈的地址就越低。栈(stack)的顶 部在可读写的 RAM 区的最后。因为栈是有限度的.因此.无限递归之类调用 会将栈空间用关. 堆段(heap):用于动态内存分配(dynamic memory allocation)。所有 malloc 的分配的 所有 内存空间都是从这一个区域分配出来 内存空间都是从这一个区域分配出来.保存函数内部动态分配内存,是另外一种用来 保存程序信息的数据结构,更准确的说是保存程序的动态变量。 堆是“先进先出”(First In first Out,FIFO)数据结构。它只允许在堆的一端 插入数据,在另一端移走数据。堆的地址空间“向上增加”,即当堆上保存的 数据越多,堆的地址就越高。 下图显示了这 5 个段在内存中的典型排列。这是一张逻辑图,表示了一个程序在内存中 看起来是怎么样的。对于一个给定的实现,没有强制的要求说必须按照这种方式来排列 这 5 个段。然而,这给了我们一种典型的便于描述的排列方式。 ,代码段(text segment) 从地址 0x 开始(往上),栈底从地址 0xC0000000 往下(在这个特定的表示结构 中,栈段从高地址向低地址扩展) 。在堆顶和栈顶之间的虚拟地址空间是很大的(这保 证了 2 个段不会互相干扰) 。 关于各个变量在分配段的情况 在 Windows 下可以用 cl /FA test.cpp 这一命令在编译时同时导出 asm 语言,汇编语言清 晰显示各段分配情况, 其中 cl.exe 是 VC++的命令行编译器,IDE 实际调用 cl.exe 自动编 译,手工调用是因为写入/FA 命令行参数./FA 表示产生跟源码同名,后缀名为.asm 汇编文 件. 在 Linux 下,直接对带有调试信息的可执行文件执行 nm 命令即可得出详细分段情况 以下是编译结果 TITLE test_cl.cpp .386P include listing.inc if @Version gt 510 .model FLAT else _TEXT SEGMENT PARA USE32 PUBLIC 'CODE' _TEXT ENDS _DATA SEGMENT DWORD USE32 PUBLIC 'DATA' _DATA ENDS CONST SEGMENT DWORD USE32 PUBLIC 'CONST' CONST ENDS _BSS SEGMENT DWORD USE32 PUBLIC 'BSS' _BSS ENDS _TLS SEGMENT DWORD USE32 PUBLIC 'TLS' _TLS ENDS FLAT GROUP _DATA, CONST, _BSS ASSUME CS: FLAT, DS: FLAT, SS: FLAT endif PUBLIC ?a@@3HA ;a PUBLIC ?c@@3HA ;c _BSS SEGMENT ?a@@3HA DD 01H DUP (?) ;a _BSS ENDS _DATA SEGMENT _b DD 03H ?c@@3HA DD 04H ;c _DATA ENDS PUBLIC _main EXTRN _printf:NEAR _BSS SEGMENT _d DB 0aH DUP (?) _BSS ENDS _DATA SEGMENT $SG582 DB 'hello !', 00H $SG583 DB '%d,%d,%d,%s,%d', 00H _DATA ENDS _TEXT SEGMENT _e$ = -4 _main PROC NEAR ; File test_cl. Line 7 push ebp mov ebp, Line 8 mov DWORD PTR _e$[ebp], 3 ; Line 9 push OFFSET FLAT:$SG582 call _printf add esp, 4 ; Line 10 mov eax, DWORD PTR _e$[ebp] push eax push OFFSET FLAT:_d mov ecx, DWORD PTR ?c@@3HA ;c push ecx mov edx, DWORD PTR _b push edx mov eax, DWORD PTR ?a@@3HA ;a push eax push OFFSET FLAT:$SG583 call _printf add esp, 24 ; H ; Line 11 mov esp, ebp pop ebp ret 0 _main ENDP _TEXT ENDS END.bss 段和 段和.data 段的区别用 cl.exe(VC++编译器,可以从命令行直接调用)编译两个小程序如下:/* program1.cpp*/ int ar[30000]; void main() { printf(“hello”); } } /* program2.cpp*/ int ar[300000] = {1, 2, 3, 4, 5, 6 }; void main() { printf(“hello”);可以发现 program2 编译之后所得的.exe 文件比 program1 的要大得多。这是因为未初始化的 变量会保存 BSS 段,并且只是这个段里放一个 ar 标识符,并未占用太多空间.所以程序 1 较小, 而程序 2 中的 ar 被初始化,将在 data 段中展开,因为整个程序也变得较大 这个可以用 cl/FA program1.cpp 和 cl /FA program2.cpp 来分别用汇编语言的结果来查看结果栈区与 Data 区的区别 /* test_data1.c */ int main(){ char* p = “Hello World1”; char * p1= “Hello World1”; char a[] = “Hello World2”; a[2] = ‘A’; p[2] = ‘A’;/* test_data2.c*/ void func(char *p) { p[0]=’A’; } int main() { func(“hello,the world”); }这个程序在 p[2]=’A’;处就会崩溃, 是变量 p 和变量数组 a 都存在于栈区的(任何临时变量都是处于栈区的,包括在 main() 函数中定义的变量) 。但是,数据 “Hello World1”和数据“Hello World2”是存储于不同的区域 的。因为数据“Hello World2”存在于数组中,所以,此数据存储于栈区,对它修改是没有任 何问题的。 因为指针变量 p 仅仅能够存储某个存储空间的地址,数据“Hello World1”为字符串常量, 所以存储在 Data 段中。 虽然通过 p[2]可以访问到静态存储区中的第三个数据单元, 即字符‘l’ 所在的存储的单元。但是因为数据“Hello World1”为字符串常量,不可以改变,所以在程序 运行时,会报告内存错误。并且,如果此时对 p 和 p1 输出的时候会发现 p 和 p1 里面保存的 地址是完全相同的。换句话说,在数据区只保留一份相同的数据 test_data2 存在同样的问题,即在 p[0]=’A’;处试图修改 Data 区数据.会立即造成程序崩溃栈区与堆区的区别 char* f1() { char* p = NULL; p = &a; } char* f2() { char* p = NULL: p =(char*) malloc(4); } void test(char * p) { int i=3;*p=’a’;printf(“i=%d\n”,i); }main(){char* p = f1(); test(p); *p = ‘a’;}在这个例子中,执行了 test()后,可以发现 i 的值并没有显示为 3,是因为 i 所占空间的被 *p=’a’被破坏,因此对栈空间破坏比对 data 空间破坏更为危险,因为这个 BUG 不会被立即导致 程序被崩溃,而是以一种隐蔽的错误在系统中慢慢扩散 . 此时,编译并不会报告错误,但是在程序运行时,会发生异常错误。因为,你对不应 该操作的内存(即,已经释放掉的存储空间)进行了操作。但是,相比之下, f2()函数不会 有任何问题。因为,malloc 是在堆中申请存储空间,一旦申请成功,除非你将其 free 或者程 序终结,这块内存将一直存在。也可以这样理解,堆内存是共享单元,能够被多个函数共同 访问。如果你需要有多个数据返回却苦无办法,堆内存将是一个很好的选择。但是一定要避 免下面的事情发生:void f() { … char * p = (char*)malloc(100); … }这个程序做了一件很无意义并且会带来很大危害的事情。因为,虽然申请了堆内存,p 保存了堆内存的首地址。但是,此变量是临时变量,当函数调用结束时 p 变量消失。也就是 说,再也没有变量存储这块堆内存的首地址,我们将永远无法再使用那块堆内存了。但是, 这块堆内存却一直标识被你所使用(因为没有到程序结束,你也没有将其 free,所以这块堆 内存一直被标识拥有者是当前您的程序) ,进而其他进程或程序无法使用。我们将这种不道 德的“流氓行为”(我们不用,却也不让别人使用)称为内存泄漏。(Memory Leak) 内存泄漏。 内存泄漏 在一个应用程序的虚拟空间里,堆的尺寸远大于栈.而栈被频繁的使用,局域变量.函数参数 等都需要栈的来处理.栈比一般开发者想象还要小.有时会小到只有 4096 字节. 因此在一个应用程序,分配一个巨大的自动变量和分配一个巨大的全局变量一样,是一件 非常不明智的事情,在运行时,会立刻造成程序段错误.比较好的做法是用动态分配的方法来 分配巨大的结构./*采用栈来分配, 运行立刻会造成段错误*/ main() { char buf[25000]; strcpy(buf,”hxy”); printf(buf); }/*采用堆来分配空间*/ main() { char buf = malloc(25000); strcpy(buf,”hxy”); printf(buf); free(buf); }
更多搜索:
All rights reserved Powered by
文档资料库内容来自网络,如有侵犯请联系客服。}

我要回帖

更多关于 sql 声明变量 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信