分类: C/C++
2011-09-30 09:34:46
再复杂的程序由以下部分构成,层层分解,用这种最简单的指令实现
输入(键盘/文件/设备)-------基本运算------分支/测试-------循环-------输出(文件/屏幕/设备)
写程序就是和存储空间打交道,一定要懂得怎么使用存储空间(分配,释放,存储类型,变量,指针,生命周期)一定要清清楚楚。
分配:常用的内存分配函数/常量,变量的存储;
全局变量、静态局部变量保存在全局数据区,初始化的和未初始化的分别保存在一起。
普通局部变量保存在堆栈中。
常量分为:普通常量和文字常量(字符串常量)
普通常量编译后成为二进制程序代码的一部分。
文字常量保存在文字常量区,这部分中的内容不允许修改。
释放:生命周期到了及时释放/使用完了提前释放,常用的内存释放函数
存储类型:某种变量存储类型占用多大的存储空间,明白这里,使用指针操作时很重要
变量:就是一块内存空间,其值可变,明白什么类型的变量可以有多大的存储空间,赋值时不要搞错,超过了存储的空间,例如整形的变量赋一个字符串。
指针:根据指针的类型,准确的取到想要的数据
调BUG:编译错误-----运行错误-----语义错误
想到最有可能出错的地方,改一点调试一下,是否符合预期,否则换思考方向
写程序要有信心,保证思路清晰;
知道自己在写什么(每一句的意思),写的语句不要有语义错误,不确定的话,马上查阅,及时消除低级错误(防微杜渐);
不要忽略警告,使用-Wall选项,提示警告
积累调试经验,记住错误提示及相应的解决办法
堆还是栈,都要防止越界现象的发生,因为越界的结果要么是程序崩溃
赋值/基本运算:类型一定要匹配,不行就强制转换
副作用:函数,变量,表达式
慎用全局变量:一旦出错很难找到什么时刻被改变,最好用形参传参代替
全局变量:定义了就要初始化,且要用常量表达式初始化,不做初始化默认为0
局部变量定义时不初始化,其值不确定,即每次运行都可能得到一个随机值
Case后面跟的表达式必须是常量表达式,且是整形的
Return返回到调用函数的地方,无论有多少重循环
Break:只能跳出一重循环,不能跳出函数
Int i;
Int *pi=&i; //pi占4个字节,保存i的虚拟地址(32位)
Char c;
Char *pc=&c;//pc占4个字节,保存c的虚拟地址(32位)
对和栈的主要的区别由以下几点:
1、管理方式不同;
2、空间大小不同;
3、能否产生碎片不同;
4、生长方向不同;
5、分配方式不同;
6、分配效率不同;
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:
打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)
对了,还有一件事,如果有人把堆栈合起来说,那它的意思是栈,可不是堆,呵呵,清楚了?
全局变量和静态全局变量的区别
全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。
而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效,
在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。
Static
1. 静态变量,分配在静态存储区,在数据段中。函数退出之后,变量值不变。
2. 作用域,全局的静态变量、静态函数只能在本文件中使用。(不同于一般全局变量)
局部的静态变量同函数的局部变量
http://blog.csdn.net/hairetz/article/details/4141043
一、预备知识—程序的内存分配
一个由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其
操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回
收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的
全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另
一块区域。
- 程序结束后由系统释放。
4、文字常量区
—常量字符串就是放在这里的。
程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
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字节的区域就在堆区。
strcpy(p1, "123456"); 123456/0放在常量区,编译器可能会将它与p3所指向的"123456"
优化成一个地方。
}
二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。
例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空
间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1
= (char *)malloc(10);
在C++中用new运算符
如p2
= new char[10];
但是注意p1、p2本身是在栈中的。
2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢
出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表
中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的
首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。
另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部
分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意
思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有
的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将
提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储
的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小
受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是
直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。
2.5堆和栈中的存储内容
栈:
在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可
执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈
的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地
址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
2.6存取效率的比较
char s1[] =
"aaaaaaaaaaaaaaa";
char *s2 =
"bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] =
"1234567890";
char *p
="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a =
c[1];
00401067 8A 4D F1
mov cl,byte ptr
[ebp-0Fh]
0040106A 88 4D FC
mov byte ptr [ebp-4],cl
11: a =
p[1];
0040106D 8B 55 EC
mov edx,dword ptr
[ebp-14h]
00401070 8A 42 01
mov al,byte ptr
[edx+1]
00401073 88 45 FC
mov byte ptr
[ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到
edx中,再根据edx读取字符,显然慢了。
2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就
走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自
由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由
度大。
(经典!)
c/C++编译的程序占用的内存情况 文字常量区
首先看下下面这段:
int main()
{
char *name =
"fengkewei";
char name1[] = "fengkewei"; //都是在栈区
char *name2 = "fengkewei";
char *name3 = "woaifengkewei";
int i = 10;
int j = 10;
int k = 11;
return 0;
}
若您觉得它们应该都保存在内存中的一块地方,那请往下看。。。。
下面是编译器为各个变量分配的内存地址,由于分配在栈上 所以地址是递减的
---------------------栈区------------------------------------
+ &name
0x0013ff5c unsigned char *
*
+ &name1 0x0013ff48 unsigned char [10]*
+ &name2 0x0013ff3c
unsigned char * *
+ &name3 0x0013ff30 unsigned char * *
+ &i 0x0013ff24 int *
+ &j 0x0013ff18 int *
+ &k 0x0013ff0c
int *
---------------------------------------------------------------
&name 和&name1[] 相差20个字节
但"fengkewei"只有10个字节(带空字符)
&name1[]和&name2 相差12个字节
后面都正常了 都差12
为什么是12?
————————————————————————————————————————————
下面是各字符串的首地址 它们都在文字常量区里 相同也就相同了 不同则是递增分配的( 文字常量区也是递增分配的)
---------------------------文字常量区-------------------------------------------------
+ name 0x004156b8 "fengkewei" unsigned char *
+ name2 0x004156b8 "fengkewei" unsigned char *
+ name3 0x00416010 "woaifengkewei" unsigned char *
+ name1 0x0013ff48 "fengkewei" unsigned char [10]
可见name1[]是在一个不同的地方的 也就是说它就是在栈上
现在我们设置一个整型指针
int *p;
p = &i;
那p的地址应该是指向i的地址,指向栈区
果然:
+ &i 0x0013ff24 int *
+ p 0x0013ff24 int *
--------------------------------------------------------------------
那当我设置一个字符型指针呢?
char *p;
p = name;
+ &name 0x0013ff5c
unsigned char * *
+ name
0x004156b8 "fengkewei" unsigned char *
+ p 0x004156b8
"fengkewei" unsigned char *
p则指向的是字符串的地址,也就是指向文字常量区
------------------------------------------------------------------------------------------
为什么&name与name的地址不同呢?
因为name是个字符型变量,该变量的空间分配在栈区,name指向的字符串在文字常量区,
而name又是个字符型指针变量,它里面所保存的值即为字符串在文字常量区的地址,这也就是为什么指针指向的内容和指针自身
的地址不同的原因了。
可见 字符串常量是放在文字常量区的, 当你初始化赋值的时候 ,这些常量就先在文字常量区开辟一段空间,保存此常量。
以后相同的常量就都在这里了。
还有 char name1[] =
"fengkewei";
+ name1 0x0013ff48 "fengkewei" unsigned char [10]
- &name1 0x0013ff48 unsigned char [10]*
[0] 102 'f' unsigned char
[1] 101 'e' unsigned char
[2] 110 'n' unsigned char
[3] 103 'g' unsigned char
[4] 107 'k' unsigned char
[5] 101 'e' unsigned char
[6] 119 'w' unsigned char
[7] 101 'e' unsigned char
[8] 105 'i' unsigned char
[9] 0 unsigned char
可以看出 name1始终是指向一个地址的 这个地址 就是栈区的地址 这就可以理解为什么书上说name1就是表示数组的首地址了。
但后面的101,102,103,...代表什么呢?是ASCII码。
看看文字常量区:
+ name 0x004156b8 "fengkewei" unsigned char *
name[0] 102 'f' unsigned char
name[1] 101 'e' unsigned char
name[2] 110 'n' unsigned char
也就是说不管在栈区还是在文字常量区,都是这样保存的。
但如果是这样呢:
char name4[20];
strcpy(name4, "fengkewei");
+ &name4 0x0013fee4
unsigned char [20]*
+ &name4[0] 0x0013fee4 "fengkewei" unsigned char *
+ &name4[1] 0x0013fee5 "engkewei" unsigned char *
+ &name4[2] 0x0013fee6 "ngkewei" unsigned char *
name4[0] 102 'f' unsigned char
name4[1] 101 'e' unsigned char
可见 每个数组元素的地址都在栈区
而它们的值保存的都是相应的字符。也就是说 这个strcpy()并没有调用文字常量区的"fengkewei",
而是直接把内容放在栈区name4[20]了.
同样
char name5[20] = "fengkewei";
+ name4 0x0013fee4 "fengkewei"
unsigned char [20]
+ name5 0x0013fec8 "fengkewei" unsigned char [20]
可见 这两种方式是将"fengkewei"保存在栈区的.
最后总结下文字常量区的保存方式:
char *name = "fengkewei";
+ name 0x004156b8
"fengkewei" unsigned char * //name的值是保存在文字常量区"fengkewei"的地址
+ &name 0x0013ff5c
unsigned char * * //name自身的地址则被编译器分配在栈区
name[0] 102 'f' unsigned char //name的第一个字符值为'f'
+ &name[0] 0x004156b8 "fengkewei" unsigned char
* //它保存在文字常量区
name[1] 101 'e' unsigned char //第二个字符为'e'
+ &name[1] 0x004156b9 "engkewei" unsigned char
* //它也保存在文字常量区
由此可见, 字符串常量, 按保存区域的不同分为以下几种:
一种是保存在栈区 ,
char name5[20] = "fengkewei";
或 char name1[]
= "fengkewei";
一种保存在文字常量区, 即 char *name = "fengkewei";
一种保存在全局区(静态区)(下次有机会再说。。。)
最后一种保存在堆区,即用malloc, alloc, realloc 分配内存分配的区域,可有程序员自身分配和释放
网上帖子很多 由于篇幅问题这里