目录:
一.内存空间
1.1 代码区(text segment)
1.2 静态全局区
1.3 堆区
1.4 栈区
二.变量在内存中的作用域和未初始值
2.1 变量的作用域:变量起作用的范围
2.2 不同变量的作用域
三. 指针的步长
3.1 为什么要定义指针的步长这一概念?
3.2 什么是指针的步长?
四.野指针
4.1什么是野指针?
4.2 为什么说野指针的指向是随机的,不可操作的?
4.3 如何避免野指针?
4.4 如果我们定义了一个野指针,肯定是不能正常使用的,那么在定义后系统编译是否会报错呢?
4.5 野指针的典型案例
五.空指针(NULL指针)
5.1 什么是空指针?
5.2 空指针的作用是什么?
六.万能指针((void*)类型指针)
6.1 什么是万能指针?
6.2 为什么要使用万能指针?
6.3 使用万能指针的例子
一.内存空间
程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的。程序运行期间不能改变。然后运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区和堆区。
1.1 代码区(text segment)
加载的是可执行文件代码段,所有的可执行代码都加载代码区,这块内存在运行期间不可修改。
1.2 静态全局区
静态全局区就是存放那些静态和全局变量的。这些变量都是在执行main函数前就已经开辟了空间,只有在程序结束后才会释放空间。除了存放静态和全局变量以外,这个区还会存放文字常量。
我们在上面的示意图中可以看到,这个区分为两个不同的小区:
1>未初始化数据区(BSS):
加载的是可执行文件BSS段,位置可以分开也可以紧靠数据段,存放的是全局未初始化、静态未初始化的数据。
2>全局初始化数据区/静态数据区(data segment)
加载的是可执行文件数据段,存放的是全局、静态已初始化的数据。
此外,除了存储全局、静态已初始化数据,它还存储文字常量(只读)。
1.3 堆区
堆是一个大容器,它的容量很大,远远大于栈,但没有栈那样的先进后出顺序。这个空间一般用于动态内存分配(通过malloc()函数实现)。在内存中,堆则位于BSS区和栈区之间。一般由程序员分配和释放。如果程序员不释放的,程序结束后就会由操作系统回收。
1.4 栈区
栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放。所以,局部变量的生存周期为申请到释放该段的栈空间。
二.变量在内存中的作用域和未初始值
2.1 变量的作用域:变量起作用的范围
·代码块作用域(代码块是{}之间的一段代码)
·函数作用域
·文件作用域
2.2 不同变量的作用域
我们提到作用域,其实就是为了讨论变量的生命周期,所谓生命周期就是从开辟空间到释放空间这个过程。
我们一般在定义变量会同时初始化变量,以保证变量的有效性。那如果没有初始化怎么样?都会为0还是随机值?可否正常使用?
我们这里通过代码验证总结了四种不同的变量的作用域和定义时未初始化时这个变量内存空间所存放的数值:
1.局部变量 (auto自动变量)
在{ }范围之内定义的变量,并且仅在{ }内起作用
auto int a;//auto可省略
· 作用域:在定义变量的{ }之内有效
· 生命周期:程序运行变量定义处开辟空间,所在函数结束之后(或{ })释放空间
· 未初始化的值:随机
2. 静态(static)局部变量
在{ }范围之内定义的变量,在前面加上了static修饰的变量。
static int a;
· 作用域:在定义变量的{ }之内有效
· 生命周期:和程序运行周期相同,因为静态变量存放在静态全局区,在执行main函数之前就已经开辟了空间,程序结束之后才释放空间。
· 未初始化的值:0(注:静态局部变量只初始化一次)
3.全局变量
在函数之外定义的变量
· 作用域:整个工程,所有文件
· 生命周期:和程序运行周期相同,因为全局变量存放在静态全局区,在执行main函数之前就已经开辟了空间,程序结束之后才释放空间
· 未初始化的值: 0
4.静态全局变量
在函数之外定义的变量,加上static修饰的变量
static int a
· 作用域: 当前文件
· 生命周期: 和程序运行周期相同,因为静态全局变量存放在静态全局区,在执行main函数之前就已经开辟了空间,程序结束之后才释放空间
· 未初始化的值: 0
再进一步总结一下:
作用域:局部变量和静态局部变量的作用域在{ }内,全局变量在整个工程,局部变量在当前文件
生命周期:除了局部变量为从变量定义时开辟,函数结束后释放空间。其他都为在执行main函数之前就已开辟空间,程序结束后才释放空间。(即除了局部变量,其他的生命周期都为整个程序)
未初始化的值:除了局部变量为未初始化的值为随机值,其他都为0
三. 指针的步长
3.1 为什么要定义指针的步长这一概念?
我们知道,同一编译器下的内存大小都是一定的,而所谓地址也就是内存的编号,也就是说地址的范围也是固定的。(地址=指针=内存的编号)。既然指针变量存放的都是地址,也就是说,不管什么类型的指针变量,它的大小都是相同的。换个角度说,不管什么类型的指针,大小只与系统内存有关,只与编译器有关。
那么,在32位编译器中,指针变量的大小为4字节;而在64位编译器中,指针变量的大小则为8字节。
那么我们就会疑惑了。既然指针变量的大小都是一样的,那为什么我们还要定义不同类型的指针变量?
这是因为,虽然指针变量存放的都是地址,指针变量的大小都相同,但是不同类型的指针变量,取指针指向的内容的宽度是与类型有关系的。如果是char*类型的指针,在取内容时他只能取出一个字节的内容,而如果是int*类型的指针,在取内容时他只能取出4个字节的内容。
我们发现,不同指针类型的指针宽度是不同的。于是乎,就有了步长的概念。
3.2 什么是指针的步长?
所谓步长,就是不同类型的指针变量,取指针指向的内容宽度。
在代码中,步长就是指针变量+1所跨过的字节大小。
步长的大小往往等于该指针变量所存储的类型的地址对应的那个类型的大小。
四.野指针
4.1什么是野指针?
野指针,就是没有初始化的指针。野指针的指向是随机的、不可操作的。
4.2 为什么说野指针的指向是随机的,不可操作的?
首先,如果我们在栈区申请了一个局部变量指针,由于局部变量未初始化的值是随机的,那么指针在定义后就会随机保存一个地址,而这个地址可能指向内存中任何一个可能的地方,包括代码区、文字常量区。 而为了避免这样的情况发生,操作系统不允许操作一个指向区域是未知的指针所指向的内存空间,说的有点绕,就是操作系统不允许操作野指针指向的内存区域,因为它是未知的,不可操作的。
而如果在静态全局区申请了一个指针,如果没有初始化,则系统默认执行0x0000 0000,也就是NULL,这个地址存放的也是代码区,而代码区是用来加载可执行代码的。在运行期间这块内存不能修改。也就是说野指针的指向还是不可操作的。
总结一下,也就是说:
我们使用指针的原则:指针保存的地址一定是定义过的。
4.3 如何避免野指针?
使用野指针,也就是操作一个未初始化的指针所指向的空间怕是我们最容易犯的一个错误。
如果我们保证使用指针的原则,指针保存的地址一定是定义过的,就可以避免野指针的存在。我们要避免野指针其实就是要避免指针乱指,指向了不可操作的内存区,比如文字常量区和代码区等。
我们一般避免的方式有两种:
1.在定义后就让该指针指向一个已定义过的变量所处的内存空间
2.在堆区申请一块内存,让该指针直接指向这个在堆区的地址。因为堆区就是提供给用户动态申请和释放内存的大容器,是可操作的。
4.4 如果我们定义了一个野指针,肯定是不能正常使用的,那么在定义后系统编译是否会报错呢?
答案是不会。前面我们提到过,操作系统不允许操作一个指向区域是未知的指针所指向的内存空间,也就是说操作系统不允许操作一个野指针。但是,野指针不会直接引发错误,因为野指针就是一个指针变量,是变量就可以像整型等其他变量一样可以任意赋值,只需保证不越界即可。
这里我们总结一下:
野指针不会直接引发错误,操作野指针指向的内存空间才会出问题。
4.5 野指针的典型案例
int a = 100;
int *p;
p= a; //将a的值赋值给指针变量p,p为野指针,不会有问题,但没有意义
p= 0x12345678;//给指针变量p赋值,p为野指针,不会有问题,但没有意义
*p = 1000;//野指针指向未知区域,内存出问题,err
五.空指针(NULL指针)
5.1 什么是空指针?
我们前面提到过,NULL也就0x0000 0000,空指针就是指向NULL这个地址的指针。
例:int *p =NULL;
5.2 空指针的作用是什么?
我们前面提到过0x0000 0000是处在代码区的,那么这个空指针是不可操作的。那为什么我们要专门让指向NULL这个空间的指针定义为空指针呢?
这里我们可以结合NULL这个地址在运行过程中是不允许被使用的特性来思考:如果我们操作空指针,就是非法操作。而很多时候,比如完成一个初始化的代码块时,我们希望某些指针只被使用一次,那我们在使用时只需要判断指针是否为NULL就可以知道这个指针是否被使用过。
除此之外,空指针还可以有什么作用呢?空指针也可以标志为没有任何指向任何变量的指针,也就是说这个空指针没有指向任何变量,也就说,这个指针是空闲可用的。
总结下,程序员使用这个地址的主要作用是:
1.如果使用完指针后,便将指针赋值为NULL,在使用时只需要判断指针是否为NULL就可以知道是否被使用。
2.标志着这个指针没有指向任何变量,是空闲可用的。
六.万能指针
6.1 什么是万能指针?
所谓万能指针,就是可以保存任意类型变量的地址。我们编写代码时,就将(void *)类型的指针称为万能指针。
例:(void *) p=NULL;
6.2 为什么要使用万能指针?
我们前面提到了指针步长的概念,那么我们知道如int、float、char等基本类型的变量的地址只能存放在int*、float* 、char*类型的指针变量中,我们在操作时才能正常引用。
可是如果我们需要保存很多不同类型的或者不确定类型变量的地址怎么办?如果保存很多不同已知类型的变量我们尚且还可以分别定义对应不同类型变量的不同类型的指针变量。那如果需要让它保存任意类型的未知变量的地址怎么办?
我们前面提过,指针变量的大小都是一定的,那么,(void *)类型的指针变量就和其他基本类型的指针变量一样,大小是固定的,可以存放任意一个地址。而系统是也可以顺利申请已知内存大小的变量。
我们前面还提到过,不同类型的指针变量的步长是不一样的。但是void并不是一个基本类型,系统根本不知道void这个类型所占的空间大小,也就是说,(void *)类型的指针变量根本就没有步长。那么,如果我们需要取内容怎么办?很简单,在取内容的时候再强制转换一下就可以了。
这里我们总结一下:
我们可以用万能指针保存任意类型变量的地址。
但如果我们要取内容,需要强制转换为对应类型的指针。
6.3 使用万能指针的例子
int a =10;
void * p =(void *)&a;
printf("%d\n",*(int *)p);
结束语:
文章整理不容易,麻烦尊重知识尊重劳动,请勿抄袭剽窃。
如果本文章对你有帮助的话希望多多支持嘿