一、静态存储区、栈区和堆区
一般来说,编译器将内存分为三部分:静态存储区域、栈、堆。静态存储区主要保存 全局变量和静态变量,栈存储调用函数相关的变量、地址等,堆存储动态生成的变量,在c中是指由malloc,free运算产生释放的存储空间,在c++中 就是指new和delete运算符作用的存储区域。
1、 静态存储分配
1) 指在编译时给数据对象分配固定的存储位置,运行时始终不变。即一旦存储空间的某个位置分配给了某个数据名,则在目标程序的整个运行过程中,此位置(地址)就属于该数据名。
2) 由静态存储分配产生的数据区称为静态数据区。
3) 静态存储分配适用于不允许递归过程或递归调用,不允许可变体积的数据结构的语言。
4) 静态存储分配的特点:简单、易于实现。
5) 用来存储初始化的全局变量和初始化的静态变量。
2、 动态存储分配
1) 指在运行阶段动态地为源程序中的数据对象分配存储位置。
2) 实行动态存储分配的语言具有的特点:
允许递归过程
允许可变数据结构(可变数组或记录等)
允许用户自由申请和释放空间
3) 这种程序在编译时无法确定运行时所需数据空间的大小,需待程序运行时动态确定
4) 有两种动态存储分配方式:栈式(stack)、堆式(heap)。
3、 栈式动态存储分配
1) 在数据空间中开辟一个栈区,每当调用一个过程时,它所需要的数据空间就分配在栈顶,每当过程工作结束时就释放这部分空间。空间的使用符合先借后还的原则。
2) 特点:先借后还,管理简单,空间使用效率高
3) 用来存储函数的参数和非静态局部变量。
4、 堆式动态存储分配
1) 在数据空间中开辟一片连续的存储区(通常叫做堆),每当需要时就从这片空间借用一块,不用时再退还。借用与归还未必服从“先借后还”的原则。
2) 堆式动态存储分配适合于用户可以自由申请和归还数据空间的语言,如C++。
3) 特点:适用范围广,容易出现碎片。如何充分利用空间是个难题。
4) 由malloc,free运算产生释放的存储空间,以及c++中new和delete运算符产生释放的存储空间都是堆空间,未初始化的全局变量和静态变量也存储在该区。
常见疑惑:常量字符串很少需要修改,存在静态存储区会提高程序的效率
const char str1[] = "abc"; //str1为数组变量,有自己的内存空间。
const char *str2 = "abc"; //str2为指针,指向静态存储区的"abc"字符串常量。
注意上述语句的区别,具体范例如下:
范例一:
#include <stdio.h>
int A()
{
int test=10;
return test;
}
int main()
{
int a=A();
printf("%d/n",a);
return 0;
}
上面的代码能编译通过 我想问 在A() 函数中的 test 变量的生存期不是只在A()函数体内吗? 怎么还能成功返回呢 ?下面的这段代码为何就不行呢 两个程序中的变量生存期有什么区别啊?
#include <stdio.h>
char* A()
{
char p[]="hello world";
return p;
}
int main()
{
char *str=NULL;
str=A();
printf("%s",str);
}
答案是:
关键在int a=A(); 实际是a=test;之后test挂掉了,但是a还存在.
而str=A(); 就是str=p. 之后p跟p指向的堆区都挂了,可是str依然指向p那片挂掉的堆区.
范例二:
C/C++ code
-
char * FuncC() { char* a="hello word" ; return a; } char * FuncB() { char a[]="hello word" ; return a; }char *FuncD(){static char a[]="hello word";return a;} int _tmain(int argc, _TCHAR* argv[]) { char *b,* c, *d; c = FuncC(); b = FuncB();d = FuncD() char a[100 ]; ::memset(a,NULL,100 ); strcpy(a,c); std::cout<<"A="<<a <<" B="<<b<<" C ="<<c <<"D="<<d<< endl; getchar(); return 0 ; }
输出结果:
-
A=hello word B=p_/*(操作未知)*/ C=hello word D=hello word;
为什么FuncB失败。FuncC和FuncD就成功呢?
解释:
FuncC中,char* a="hello word"; a并没有分配空间,"hello word"串放在静态区,放在栈中的只是指针a(4个字节的指针),函数返回后,销毁的也只是a,静态区的串是不会销毁的 。调用c = FuncC()后,虽然a被销毁了,但c接收了返回值,依然指向静态区的串"hello word"
FuncB的做法相当于在函数的栈内申请了一个数据区存放a[]="hello word"; 存在一个数据拷贝的操作:将静态数据区内的数据拷贝到栈数据区内,调用b = FuncB()后,b指向堆栈中的数据,而不是静态区的数据;当函数退出后,栈就销毁了,那b指向的数据就是未知的了。
二、小端和大端存储方式
1.什么是大端和小端
1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端,反序排序。。
2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端,依次排序。
2.数字0x12 34 56 78在内存中的表示形式
以下实例都是在Intel x86 CPU上的Windows为讲解平台(小端存储,假设内存地址从0x4000开始)。
内存地址 | 小端模式存放内容 | |
0x4000 | 0x78 | |
0x4001 | 0x56 | |
0x4002 | 0x34 | |
0x4003 | 0x12 |
3.字符串在内存中的表示形式
- char *ptr1, *ptr2;
- ptr1 = "language"; ptr2 = "programe";
-
- int k;
- for (k = 0; k < 18; k++){
- printf("0x%d:", ptr1 + k);
- printf("%c\n", *(ptr1 + k));
- }
- printf("\n");
输出结果为:
- [root@deron Mar9]# ./yabakui
- 0x8048584:l
- 0x8048585:a
- 0x8048586:n
- 0x8048587:g
- 0x8048588:u
- 0x8048589:a
- 0x804858a:g
- 0x804858b:e
- 0x804858c:
- 0x804858d:p
- 0x804858e:r
- 0x804858f:o
- 0x8048590:g
- 0x8048591:r
- 0x8048592:a
- 0x8048593:m
- 0x8048594:e
- 0x8048595:
结论:相邻字符串按照地址上升存储,(即先申请先分配)且字符串字符按照地址上升来存放。
如图:(下图跟内存一样,按照低地址在下来看)
4.字符串在内存中的表示形式
测试程序2:测试字符数组在内存中的存储位置
- char a[] = "language", b[] = "programe"; // a[]= "12345678"存储方式也是数组元素按照地址地址上升存储
- char *ptr1, *ptr2;
- ptr1 = a;
- ptr2 = b;
- int k;
- for (k = 0; k < 18; k++){
- printf("0x%x:", ptr2 + k);
- printf("%c\n", *(ptr2 + k));
- }
- printf("\n");
输出结果:
- [root@deron Mar9]# ./yabakui
- 0xbf972d06:p
- 0xbf972d07:r
- 0xbf972d08:o
- 0xbf972d09:g
- 0xbf972d0a:r
- 0xbf972d0b:a
- 0xbf972d0c:m
- 0xbf972d0d:e
- 0xbf972d0e:
- 0xbf972d0f:l
- 0xbf972d10:a
- 0xbf972d11:n
- 0xbf972d12:g
- 0xbf972d13:u
- 0xbf972d14:a
- 0xbf972d15:g
- 0xbf972d16:e
- 0xbf972d17:
结论:相邻两个字符数组按照地址下降存储(即后申明先分配),数组元素按照地址地址上升存储
如图:
通过比较字符串和字符数组在内存地址,可以知道,它们存放在内存的不同区域。