今天一大早起来,朋友就给我发给了下面一个题目,要我解释下,答案我是想出来了,可是就是解释不清楚。。。
首先要解决这个问题就必须了解程序在内存的分配:
关于堆与栈的
栈(stack):是自动分配变量,以及函数调用所使用的一些空间(所谓的局部变量),地址由高向低减少;
堆(heap):由malloc,new等分配的空间的地址,地址由低向高增长。
自己所编写的代码:
#include <iostream>
using namespace std;
void main()
{
int a;
int b;
int *p1;
int *p2;
cout < <&a < <endl < <&b < <endl < <&p1 < <endl < <&p2 < <endl < <endl;
//结果显示:0012FF7C,0012FF78,0012FF74,0012FF70;可见,栈中的地址是减少的
int *p3=new int[1];
int *p4=new int[1];
cout < <p3 < <endl < <p4 < <endl;
//结果显示:003907A8,003907E0;可见,堆中地址是增加的
}
有一点应该注意:频繁使用heap 会产生内存碎片,而按照堆栈的先入后出原则,即先申请的后释放原则可以有效地避免在堆中产生碎片。
栈区中的局部数据占有的空间在函数结束后会自动释放,而堆中的要由程序员手动释放。此外,栈中分配的空间一般于编译时就确定大小(windows是2M,这个我还不怎么理解),而若要在运行时分配空间,就得用堆。栈一般空间有限,若申请空间过大(比如int a[400000])会出现溢出(overflow).
堆和栈中的存储内容
Stack栈: 在函数调用时,第一个进栈的是主函数中 函数调用语句的下一条可执行语句的地址,然后是函数的各个参数,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
heap堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
程序的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。与数据结构栈类似
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事。
3、全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
引用的一个例子:
//main.cpp
#include <iostream>
using namespace std;
int a = 0; //全局初始化区
char *p1; //全局未初始化区
void main()
{
int b; //栈
char s[] = "abc"; //栈
char *p2; //栈
char *p3 = "123456"; //123456/0在常量区,p3在栈上。
static int c =0; //全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。注意:P2本身是在栈中的
strcpy(p1, "123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的,"123456"优化成一个地方。
}
--------------全局区----------------
+ &a 0x00432e20 int a
+ &p1 0x00432e1c ""
-------------------------------------
--------------栈区------------------地址值递减
+ &b 0x0012ff7c
+ &s 0x0012ff78 "abc"
+ &p2 0x0012ff74 "烫烫abc"
+ &p3 0x0012ff70 "郆"
-------------------------------------
下面就来看这道题吧:
#include <iostream>
using namespace std;
void main()
{
char *p1 = "abc";
char *p2 = "abc";
char str1[] = "abc";
char str2[] = "abc";
if(p1 == p2)
{
cout<<"p1 == p2"<<endl;
}
else
{
cout<<"p1 != p2"<<endl;
}
if(str1 == str2)
{
cout<<"str1 == str2"<<endl;
}
else
{
cout<<"str1 != str2"<<endl;
}
}
输出结果如下:
p1 == p2
str1 != str2
下面是编译器给各个变量分配的内存地址
------------------文字常量区---------------
+ p1 0x0042f054 "abc"
+ p2 0x0042f054 "abc"
+ str1 0x0012ff74 "abc"
+ str2 0x0012ff70 "abc"
---------------------------------------------
------------------栈区---------------------
+ &p1 0x0012ff7c "T養"
+ &p2 0x0012ff78 "T養"
+ &str1 0x0012ff74 "abc"
+ &str2 0x0012ff70 "abc"
-------------------------------------------
发现p1跟p2的值的确是相等的,str1跟str2的值的确是不相等的,为什么呢?
指针p1与p2指向同一个内存区(文字常量区),str1,str2则分别指向不同的内存区
上面黄色部分说明p1,p2,str1,str2这几个变量被分配在一个连续的区域内(栈区),而str1与str2
变量里存储的值就是str1,str2的地址值。
再看下面两个变量的地址
+ &str1[0] 0x0012ff74 "abc"
+ &str1 0x0012ff74 "abc"
发现其实str1[0]与str1其实同一个变量,所以说字符串数组名保存其实是字符串字面值的第一个元素。
而字符串指针里存储的内容是指向字符串字面值的第一个元素的地址,指针自身还需要开辟一个内存来存储指针变量。
对比文字常量区及栈区的内容可知:
1.字符指针指向的字符串是存储在文字常量区的,而字符指针本身是在栈区的。
2.字符数组中存储的字符串是存放在栈区内的,而不在文字常量区另外开辟空间。
通过上面的分析,我们得出的结论:
1.p1==p2是因为P1与P2都存储了保留在文字常量区的abc的地址,地址相同,p1,p2的值当然就相等;
2.str1!=str2是因为他们都在栈区里开辟了不同的内存空间来存储“abc”字符串。
由此可见, 字符串常量, 按保存区域的不同分为以下几种:
一种是保存在栈区 , char a[3] = "abc";
一种保存在文字常量区, 即 char *a = "abc";
一种保存在全局区(静态区)
最后一种保存在堆区,可有程序员自身分配和释放