一、数据类型的本质
1、数据类型可以理解为创建变量的模具,是固定内存大小的别名。
2、数据类型的作用:编译器预算对象(变量)分配内存空间大小
二、void
1)、void简介
void 的字面意思是“无类型”,void*则为“无类型指针”,void *可以指向任何数据类型。在c中不存在void类型的变量,因为c语言就没有定义void究竟是多大内存的别名。c语言中void有两种使用方式:
1、用于修饰函数的返回值和函数参数,仅表示无,如果一个函数没有返回值,怎将其声明为void,如果没有参数,应该声明
其参数为void
2、用于数据类型的封装,例如: int InitHardEnv(void **handle)
2)、void指针的意义
c语言规定只有相同数据类型的指针才可以相互赋值,因此void*指针作为左值用于“接收”任意类型的指针。void*指针作为右值赋值给其他指针时,需要进行强制类型转换。
三、变量
1)、概念:
既能读又能写的内存对象,我们称为变量;一旦初始化后不能修改的对象我们称之为常量。
2)、变量的本质
a、程序通过变量来申请和命名内存空间,例如:int a =0 。程序通过变量名访问内存空间(变量名实质是
一段连续内存空间的别名)
四、C语言程序的内存四区模型。
1)、内存四区的建立流程
流程说明:
a、操作系统把物理硬盘代码load代内存(运行.exe文件)
b、操作系统把c代码分成四个区
c、操作系统找到main函数入口执行程序。
2)各个区元素分析:
栈区(stack) | 由编译器自动分配释放,存放函数的参数值,局部变量值等 |
堆区(heap) | 一般由程序员分配与释放(动态内存申请与释放),若程序员不释放,程序结束后可能由操作系统进行回收 |
全局区(静态区)(static) |
全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的 全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。 |
常量区 | 字符串常量和其他常量的存储位置,程序结束后由操作系统进行释放 |
程序代码区 | 存放函数体的二进制代码 |
3)静态存储区的理解:
执行面代码:
#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h> char * getStr1() { char *p1 = "abcdefg2"; return p1; } char *getStr2() { char *p2 = "abcdefg22"; return p2; } void main() { char *p1 = NULL; char *p2 = NULL; p1 = getStr1(); p2 = getStr2(); //打印p1 p2 所指向内存空间的数据 printf("p1:%s , p2:%s \n", p1, p2); //打印p1 p2 的值 printf("p1:%d , p2:%d \n", p1, p2); printf("hello...\n"); system("pause"); return; }
输出:
p1:abcdefg2 , p2:abcdefg22 p1:19229488 , p2:19229500 hello... 请按任意键继续. . .
我们看到输出的p1和p2的值不一样,其指向的内存的地址的内容也不一样。那么我们将代码做如下修改:
将函数getstr1()
char *p1 = "abcdefg2";
改为下面语句
char *p1 = "abcdefg22";
这个时候我们的指针p1 和 p2 所指向的内从空间的值就已经相同了,那么此时p1和p2的值相等吗?
我们运行代码输出:
p1:abcdefg22 , p2:abcdefg22 p1:16870192 , p2:16870192 hello... 请按任意键继续. . .
p1 此时和 p2 的值是相等的。我们从内存四区中的静态存储区来看这段代码:
函数从main()函数入口执行
char *p1 = NULL; char *p2 = NULL;
此时对应的内存四区模型为:
编译器分别将变量p1,p2在栈区压栈,并给定初始值为NULL
p1 = getStr1();
当执行到这句代码时,编译器先进性函数调用,进入getstr1函数中去(我们假设全局区首地址为0xaa11):
char * getStr1() { char *p1 = "abcdefg2"; return p1; }
当将函数的返回值赋给p1的时候,编译器会将getstr()函数在运行期其间申请的所有的栈区内存进行释放。内存四区如下:
同一样,当执行p2 = getStr2();语句时基本的内变化和p1相同,唯一不同的是编译器会对我我们的代码进行优化,p2指向的内存的值和p1相同时,编译器会只保留一个。这样内存四区如下:
4)堆区的理解:
我们根据下面代码来帮助我们理解堆区,堆区内存是由程序员申请和释放的,由操作系统进行管理,只有当程序进行完后,未被程序员释放的内存才会被释放。
#include <stdlib.h> #include <string.h> #include <stdio.h> //堆 char *getMem(int num) { char *p1 = NULL; p1 = (char *)malloc(sizeof(char) * num); #申请内存空间 if (p1 == NULL) { return NULL; } return p1; } void main61() { char *tmp = NULL; tmp = getMem(10); if (tmp == NULL) { return; } strcpy(tmp, "111222"); //向tmp做指向的内存空间中copy数据 printf("hello..tmp:%s.\n", tmp); system("pause"); return; }
输出:
hello..tmp:111222. 请按任意键继续. . .执行
char *tmp = NULL;
内存四区:
当执行到getMem()函数体内的时候(假设堆的首地址是0xaa11)
将返回值赋给tmp时:
执行strcpy()函数之后。
5)栈区的理解:
我们根据下面代码来帮助我们理解堆区,栈区内存由编译器自动分配与释放
#define _CRT_SECURE_NO_WARNINGS #include <stdlib.h> #include <string.h> #include <stdio.h> //栈 //注意 return不是把内存块 64个字节,给return出来, //而是把内存块的首地址 ,返回给 tmp // 理解指针的关键,是内存. 没有内存哪里的指针 char * getMem2() { char buf[64]; //临时变量 栈区存放 strcpy(buf, "123456789"); return buf; } void main() { char *tmp = NULL; tmp = getMem2(); printf("hello..tmp:%s.\n", tmp); system("pause"); return; }
输出:
hello..tmp:烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫\?烫烫烫烫烫烫烫烫8??-. 请按任意键继续. . .
程序出现了输出出现了乱码,我们从内存的角度分析:
char *tmp = NULL;
当执行到getMem2()函数体内的时候(假设buff的首地址是0xaa11)
当我们的getMem()函数return时,编译器会将属于该函数的栈区的内存数据进行清空,但是buff的值还是会赋给tmp。
我们发现此时因为栈区的buff已经在函数执行完毕时,被编译器释放掉了,tmp此时指向的栈区的内存的是一个随机值,因此我们输出是乱码。
C语言可以在栈上分配内存,可以在堆上分配作内存,因此作为C语言的学习者我们一定要理解内存,时刻知道我们的内从从哪里来(堆区,栈区,全局区),到哪里去(释放)