问题提出:
看输出结果,使用c1、c2、c3输出字符串都相同,但c1、c2、c3本身的内容不相同。9620588 1899312看出,完全是两块地方,推断9620588属于常量区,1899312属于栈区。1899324 1899312 1899300这三个数可以看出指针区域为栈区。
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
char *c1 = "abc";
char c2[] = "abc";
char *c3 =(char*)malloc(3);//c3在栈区,分配得来的3个字节在堆区
if (c3 == NULL)
{
printf("Allocate error!");
}
c3 = "abc"; // abc\0在常量区,赋值将会把字符串"abc"首地址给c3,改变了c3的地址,如果使用使用free(c3)会报错
printf("%d %d %s\n", &c1, c1, c1);
printf("%d %d %s\n", &c2, c2, c2);
printf("%d %d %s\n", &c3, c3, c3);
//free(c3);
getchar();
return 0;
}
运行结果:
一、程序的内存分配
C语言代码(文本文件)形成可执行程序(二进制文件),需要经过编译-汇编-链接三个阶段。编译过程把C语言文本文件生成汇编程序,汇编过程把汇编程序形成二进制机器代码,链接过程则将各个源文件生成的二进制机器代码文件组合成一个文件。
C语言编写的程序经过编译-汇编-链接后,将形成一个统一文件,它由几个部分组成。在程序运行时又会产生其他几个部分,各个部分代表了不同的存储区域:
1、代码段(Code或Text)
代码段由程序中执行的机器代码组成。在C语言中,程序语句进行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。
2、只读数据段(RO data)
只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表达式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。
3、已初始化读写数据段(RW data)
已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并具有初值,以供程序运行时读写。
4、未初始化数据段(BBS)
未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。
5、堆(heap)
堆内存只在程序运行时出现,一般由程序员分配和释放,在具有操作系统的情况下,如果程序没有释放,操作系统可能在程序(例如一个进程)结束后回收内存。
6、栈(stack)
栈内存只在程序运行是出现,在函数内部使用的变量、函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。
实例:
int a = 0; //全局初始化区 data段
static int b = 20; //全局初始化区 data段
char *p1; //全局未初始化区 bss段
const int A = 10; // ro data段
void main()
{
int b; //栈
char s[] = "abc";//栈
char *p2; //p2栈上
static int c = 0; //全局(静态)初始化区,data段
char *p3 = "123456"; //123456\0在常量区,p3在栈上
p1 = (char*)malloc(10);//分配得来的10和20个字节的区域就在堆区
p2 = (char*)malloc(20);
}
二、堆和栈的理论知识
1、申请方式
栈(stack):由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。
堆(heap):需要程序员自己申请,并指明大小,在C中使用malloc函数申请内存。例如:p1=(char*)malloc(10);在C++中用new运算符,如int *p2=new int;但是注意p1、p2本身是在栈中的,而p1、p2所指向的内容在堆中。(malloc和new开辟的内存区,用free和delete释放时,要注意确保释放的内存和分配的内存是同一块内存区,例如:问题提出实例中如果使用free(c3)释放内存会报错。)
2、申请后系统的响应
栈:只要栈的剩余空间大于所申请的空间,系统将会为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大雨所申请空间的堆结点,然后将该结点从空闲中的首地址处记录本次分配的大小,这样,代码中的delete(或free)语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
3、申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在Windows下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
4、申请效率的比较
栈:由系统自动分配,速度较快,但程序员是无法控制的。
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。(另外,在Windows下,最好的方式是使用VirtualAlloc分配内存,他不是在堆,也不是在栈,而是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度最快,也最灵活。)
5、堆和栈中的存储内容
栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小,堆中的具体内容由程序员安排。
6、存取效率的比较
char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbb";
aaaaaaaaaaaaaaa是在运行时刻赋值的,而bbbbbbbbbbbbbbb是在编译时就确定的。但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
void main()
{
char a = 1;
char c[] = "1234567890";
char *p = "1234567890";
a = c[1];
a = p[1];
return;
}
对应的反汇编代码:
a = c[1];
011F5317 mov eax,1
011F531C shl eax,0
011F531F mov cl,byte ptr c[eax]
011F5323 mov byte ptr [a],cl
a = p[1];
011F5326 mov eax,1
011F532B shl eax,0
011F532E mov ecx,dword ptr [p]
a = p[1];
011F5331 mov dl,byte ptr [ecx+eax]
011F5334 mov byte ptr [a],dl
第一种是在读取时直接就把字符串中的元素读到寄存器c1中,而第二种则要先把指针值读到ecx中,再根据ecx读取字符,显然慢了。
附:
1、代码段、只读数据段、已初始化读写数据段、未初始化数据段属于静态区域,而堆和栈属于动态区域。
2、代码段、只读数据段和读写数据段将在链接之后产生,未初始化数据段将在程序初始化的时候开辟,而堆和栈将在程序的运行中分配和释放。
3、C语言程序分为映像和运行时两种状态,在编译-汇编-链接后形成的映像中,将只包含代码段(Code 或 Text)、只读数据段(RW Data)和已初始化读写数据段(RO Data)和读写数据段(RW Data)。在程序运行之前,将动态生成未初始化数据段(BSS),在程序的运行时还将动态形成堆(Heap)区域和栈(Stack)区域。一般来说,在静态的映像文件中,各个部分称之为节(Session),而在运行时的各个部分称之为段(Segment)。如果不详细区分,可以统称为段。
4、函数体中定义的变量通常是在栈上,不需要在程序中进行管理,由编译器处理。
用malloc,new等分配内存的函数所分配的内存在堆上,程序必须保证再使用free或delete释放,否则会发生内存泄漏。
5、所有函数体外定义的是全局变量,加了static后的变量不管是在函数内部或外部都放在全局区。
6、使用const定义的变量将放于程序的只读数据区。
7、栈空间主要用于一下3种数据的存储:函数内部的动态变量、函数的参数、函数的返回值。栈空间是动态开辟与回收的,在函数调用过程中,如果函数调用的层次比较多,所需要的栈空间也逐渐加大,对于参数的传递和返回值,如果使用较大的结构体,在使用的栈空间也会比较大。
2015年5月14日