=============================================================================
对于c语言来讲,内存管理是一个很重要的内容,它与指针是息息相关的,因为内存的管理都是通过指针来实现的。
-----------------------------------------------------------------------------
如果一个变量,它处在所有的代码块之外,那么它的生命周期就是和整个程序是一起的,程序启动的时候它就出现了,程序退出时,它才终止。
如果一个变量,它处在代码块之内,那么这个代码块执行的时候它才出现,代码块执行完成后,它才消失。
-----------------------------------------------------------------------------
auto int i = 0;
auto变量(自动变量)是在内存的栈里面,它是一个临时的变量,只有执行代码块的时候,它才会入栈,代码块执行完后,它才出栈。
static int i = 0;
static变量(静态变量)是在内存的静态区里面,整个程序运行期间,该变量都存在,而且静态变量只被初始化一次。
例如:
int i; for (i = 0; i < 5; i++) { static int a = 10; //定义了一个静态变量。 a++; printf("%d ", a); } 输出结果为: 11 12 13 14 15 -------------------------------------- int i; for (i = 0; i < 5; i++) { auto int a = 10; //定义了一个自动变量。 a++; printf("%d ", a); } 输出结果为: 11 11 11 11 11
-----------------------------------------------------------------------------
在代码块之外的变量都是全局变量,那么如果加了static后,依然是全局变量,但是此时变量的作用域局限在定义这个变量的文件内部。
它其实还是放在静态区的,只是外部不能访问而已。
同时函数前面也可以加一个static,如下所示:
void test() //没有static,默认该函数是全局的。
{
;
}
static void test1() //这个函数只能在定义这个函数的文件内被调用。
{
;
}
注意:函数前面加static和本身的静态区没有任何关系,因为所有的函数都放在代码区,而静态区里面放的只是变量而已,不会放函数本身。
即:static放在函数的不同位置对于c语言来讲它的意义是不一样!
-----------------------------------------------------------------------------
extern int a; //这句话的意思是:a已经定义过了,这里只是声明。
extern void test(); //这句话的意思是:函数test已经定义过了,这里只是声明。
void test(); //对于函数来说,没有extern和 有extern 对于c语言是一样的。(c语言里面一个不太好的地方)
extern int a; //这句话的意思是:明确的声明了一个变量,一定不是表示定义一个变量。
int a; //这句话的意思是:如果这个变量已经定义过了,这里就代表声明;如果这个变量没有定义过,这里就代表定义。
//即:不能确定它是定义还是声明。也即:出现了二义性,比较含糊。
-----------------------------------------------------------------------------
在一个程序加载进内存的时候,操作系统会把不同类型的数据加载进不同的区域里面,例如:
代码区:可执行代码加载进代码区;比如:所有的函数。
静态区:所有的静态变量和全局变量都加载进静态区。实际上静态区是一个综合区,它会分很多子区,其中很多常量也是在静态区另外一个区里面放的。
常量和普通静态变量有什么区别呢?
不同点:常量也是在程序运行当中一直存在的,但是常量是只读的,普通的静态变量是可读可写的。
相同点:他们的生命周期是一样的,整个程序运行的时候他们会出现在内存里面,整个程序执行完成以后他们才从内存里面消失。
栈区:栈是一种先进后出的内存结构,所有的 自动变量、函数的形参、函数的返回值 都是由编译器自动放入内存的栈中。
当一个自动变量超出其作用域时,会自动从栈中弹出。
栈区特点是:函数调用时栈出现,函数结束时栈消失。
堆区:堆和栈一样,也是一种在程序运行过程中可以随时修改的内存区域,但没有栈那样先进后出的顺序。
堆是一个大容器,它的容量要远远大于栈,但是在c语言中,堆内存空间的申请和释放需要手动通过代码来完成。
-----------------------------------------------------------------------------
c、c++会用到堆和栈,但是需要去手动维护。
c#、java也会用到堆和栈,但是不需要去手动维护。因而付出的代价是:不能够选择你到底使用的是堆还是栈呢。
作为一名使用者,使用c#、java语言,它们已经给你规定好了,有很多类,有很多对象,你尽管拿来用,但是它们这个类或者对象在哪里,我们不知道。
我们也不需要知道,它们也不让你知道,我们就算知道也没用,因为我们也管不了,也处理不了。但是对于c语言,我们可以任意去控制这个变量是出现在栈还是堆里面。
而且c语言还比较简单,因为它所有的都是变量。
而c++和java还有对象,在c++里面可以指定你的对象在栈里面还是在堆里面,即可以选择效率最高的方法来使用对象,而在java里面所有的对象都是出现在堆里面的。
这就是c和c++语言的魅力所在,以及它们做操作系统的原因之一,因为它们可以自由的控制内存中的每一个字节。
-----------------------------------------------------------------------------
用递归代码实现栈的先进后出效果(递归的过程是典型的先入栈后出栈过程)
linux下示例代码如下:
1 #include <stdio.h> 2 3 //用递归代码实现栈的先进后出效果(递归的过程是典型的先入栈后出栈过程) 4 5 void test(int n) 6 { 7 printf("%p, n = %d ", &n, n); //把代码放到递归的前面,叫做先序递归。 8 if (n < 3) 9 { 10 test(n + 1); 11 } 12 printf("%p, n = %d ", &n, n); //把代码放到递归的前面,叫做先序递归。 13 } 14 15 int main() 16 { 17 test(0); 18 19 return 0; 20 } 21 输出结果为: 22 0x7ffe5e720b0c, n = 0 第一个入栈 23 0x7ffe5e720aec, n = 1 24 0x7ffe5e720acc, n = 2 25 0x7ffe5e720aac, n = 3 26 0x7ffe5e720aac, n = 3 27 0x7ffe5e720acc, n = 2 28 0x7ffe5e720aec, n = 1 29 0x7ffe5e720b0c, n = 0 最后一个出栈
test(0) { printf("%p, n = %d ", &n, n); //0x7ffe5e720b0c, n = 0 test(1) { printf("%p, n = %d ", &n, n); //0x7ffe5e720aec, n = 1 test(2) { printf("%p, n = %d ", &n, n); //0x7ffe5e720acc, n = 2 test(3) { printf("%p, n = %d ", &n, n); //0x7ffe5e720aac, n = 3 4 < 3;不符合if的判断条件,推迟if判断语句,则继续执行剩余代码: printf("%p, n = %d ", &n, n); //0x7ffe5e720aac, n = 3 } printf("%p, n = %d ", &n, n); //0x7ffe5e720acc, n = 2 } printf("%p, n = %d ", &n, n); //0x7ffe5e720aec, n = 1 } printf("%p, n = %d ", &n, n); //0x7ffe5e720b0c, n = 0 }
-----------------------------------------------------------------------------
如果程序中申请了堆内存,但忘记了free,如果程序退出的时候操作系统会统一进行回收;但如果程序一直不退出,那么这块内存就会一直被占用,
有时更可气的是,你不但不退出程序,而且还在不停的申请新的内存,也不free,最后操作系统的内存被你“吃光了”,导致内存泄漏!
-----------------------------------------------------------------------------
例如:
int *p = malloc(200);
p = realloc(p, 400); //在p的基础上,将堆内存扩展到400个字节。
p = realloc(p, 100); //在p的基础上,将堆内存缩小到100个字节。
int *p1 = realloc(NULL,100); //如果realloc的第一个参数是NULL,那么realloc的作用和malloc是一样的。
-----------------------------------------------------------------------------
1、
1 int *test() //错误的代码 2 { 3 int i = 0; //i在栈里面,生命周期就是其所处的大括号。 4 return &i; 5 } 6 7 int main() 8 { 9 int *p = test(); //p指向了一个无效的地址。 10 *p = 10; 11 return 0; 12 } 13 -------------------------------------- 14 int *test() //正确的代码 15 { 16 int *i = malloc(sizeof(int)); //i在堆里面。生命周期很长。主动调用free,堆空间释放或者进程结束,操作系统进行内存空间回收。 17 return i; 18 } 19 20 int main() 21 { 22 int *p = test(); 23 *p = 10; 24 free(p); 25 return 0; 26 }
2、
1 void test(char *i) //错误的代码 2 { 3 i = malloc(sizeof(char) * 100); //i在栈里,指向了堆的地址。 4 } 5 6 int main() 7 { 8 char *p = NULL; 9 test(p); //实参的值可以传递给形参,形参的值发生改变,实参的值不会有影响。 10 strcpy(p, "hello"); 11 free(p); 12 return 0; 13 } 14 -------------------------------------- 15 void test(char **i) //正确代码,通过二级指针解决上面这个问题。 16 { 17 *i = malloc(sizeof(char) * 100); 18 19 } 20 21 int main() 22 { 23 char *p = NULL; 24 test(&p); 25 strcpy(p, "hello"); 26 free(p); 27 return 0; 28 } 29 小结:若想通过函数形参给实参分配内存,往往是通过二级指针来实现的。这是在c语言里面常用的技巧。 30 -------------------------------------- 31 char *test() //正确的代码 32 { 33 char *i = malloc(sizeof(char) * 100); //i在堆里面。 34 return i; 35 } 36 37 int main() 38 { 39 char *p = test(); 40 strcpy(p, "hello"); 41 free(p); 42 return 0; 43 }
3、
1 void test(char *i) //错误的代码 2 { 3 strcpy(i, "hello"); 4 } 5 6 int main() 7 { 8 test("hello"); //在栈里面:i = "hello"是常量。常量不可变。 9 return 0; 10 } 11 -------------------------------------- 12 const char *test() //正确的代码 函数的返回值是一个指向常量的指针。即该指针可以指向任何常量的地址。 13 { 14 return "hello"; //"hello"是常量。而且是字符串。所以它是const char *类型的。 15 } 16 17 int main() 18 { 19 const char *s = test(); 20 printf("%s ", s); 21 return 0; 22 }
4、
1 char *test() //错误的代码 函数的返回值是指针变量。 2 { 3 return "hello"; //"hello"是常量。实际返回值是一个常量。 4 } 5 6 int main() 7 { 8 char *s = test(); 9 strcpy(s,"aabbcc"); 10 printf("%s ", s); 11 return 0; 12 } 13 -------------------------------------- 14 const char *test() //错误的代码 15 { 16 const char a[] = "hello"; //数组a是自动变量,在栈里面。"hello"是常量,在静态区里面。从语法的角度const作用是:不能这样(a[0] = 'a';)去修改它的值。只读。 17 return a; 18 } 19 20 int main() 21 { 22 const char *s = test(); 23 printf("%s ", s); 24 return 0; 25 } 26 -------------------------------------- 27 const char *test() //正确的代码 28 { 29 static char a[] = "hello"; //此时数组a在静态区里面。 30 return a; 31 } 32 33 int main() 34 { 35 const char *s = test(); //从语法的角度const作用是:不能这样(s[0] = 'a';)去修改它的值。只读。 36 printf("%s ", s); 37 return 0; 38 }
=============================================================================