前言:在上一篇文章中说到了指针变量和普通变量的区别,那么这一篇文章中就来说说指针和数组的关联和区别,它们在很多应用场合中可以互相取代,但也在很多场合中有着自己的无法替代的作用和地位。
数组和指针的内存分配
假设我们有如下代码,分别定义了一个整型数组a和整型指针变量b,假设其编译运行的机器的机器字长为32位,一个int类型占4个字节。
int a[4];
int *b;
- 数组的内存分配:对于数组类型的变量,编译器在其声明时就给其分配一块连续的存储空间。编译其首先根据数组的类型和维数,计算这个数组需要占用多大的空间,在这个例子中,数组a需要占用的空间是4x4=16个字节的空间。分配完成后,编译器将该空间首地址与变量a相关联。需要注意的是,此时数组名a代表该数组的首地址,也就是数组第一个元素的地址。并且,当编译器分配完成后,在数组的生存期间(关于生存期的描述可见笔者以前的文章),该数组元素就存放在该地址块,不会改变a的值。因此,对于代表数组首地址的数组名,其实是一个地址常量,也就是指针常量,它可以被销毁,但是不能被重新赋值。
- 指针的内存分配:对于指针类型的变量,笔者以前提到过,不管它是指向什么类型的变量,它本身的所占的空间小大和程序编译运行的机器字长相关。在这个例子中,我们以32位机举例,以下所有提到的指针变量大小都是32位,即4个字节。所以,编译器会给b分配的存储空间大小为4个字节。
- Q:既然指针变量的大小在同一机器中是固定的,那为什么还要声明它所指向的类型,如:int,char?
- A:虽然指针大小在同一机器中是固定的,但是我们无论是用指针进行间接取值还是用+-运算对指针进行移动时,都需要知道我们取几个字节或者移动几个字节。在这个例子中,取值时,在b所指向的地址上连续取4个字节的数据;在使用b++或者b--时,不是单纯的对b的+1或者-1,而是+(1x4)或者-(1x4).
数组和指针的初始化
关于指针和数组的初始化过程,笔者还是结合32位MCU的情况进行介绍,比起强大繁复的PC系统裸机MCU更容易让人理解。
- 数组的初始化:数组的初始化可分为完全初始化和缺省初始化,二者的区别如下:
int a[5] = {0,1,2,3,4}; //对数组的每个元素都赋了值
int b[5] = {0,1,2}; //后两个元素缺省
上面两种初始化赋值方法,第二种方法缺省的元素,编译器会一致将其赋值为0.
- 指针的初始化:指针的初始化是将一个地址赋给指针变量,需要注意的是,赋给指针的变量地址所存储的变量类型必须和指针变量的类型相对应。否则,有的编译器能够警告和报错,而有的编译器仅仅给出是警告而已。当指针有指向地址后,可以通过*来间接操作它指向的值。
- Q:未对数组进行初始化?
- A:当数组未进行初始化时,编译器会根据它是否为静态变量类型进行不同处理(关于静态变量和自动变量的初始化,可见这篇文章)。如果是静态变量,那么编译器会给该数组所有元素自动初始化为0;如果是自动变量,由于函数堆栈的调用,数组被分配到的内存空间很大几率会是“脏”的,留存着上次使用的数据,对外表现为数组元素都是一些随机数。
- 因此,当我们选择很大数组作为数据结构时,要考虑将它设为自动变量的开销。因为,自动变量数组在每次调用函数时需要对其进行初始化,而这些初始化过程和赋值语句一样需要时间和空间来执行。如果将其设为静态变量,可以大大减少开销。
- Q:未对指针进行初始化?
- A:关于指针的初始化也可分为静态变量类型和动态变量类型来讨论。
- 对于静态变量类型来说,指针声明后,若没有对其初始化,通通把指针指向地址0(这时每个内存单元值都为0,详见这篇文)。在C语言中,也将NULL定义为0x00000000。所以对其赋值NULL的做法也就是将它指向零地址。
- 对于自动变量类型来说,由于函数调用频繁出入堆栈,指针被分配到的内存单元是也充满着"脏数据"。这时如果未对指针进行初始化就引用,才是真正致命的操作。因为你根本就不知道那些脏数据会指向哪一块内存的地址,以致于引起各种匪夷所思的错误。
- Q:零地址位于哪里?其内容是什么?
- A:这里笔者以ARM的Cortex-M0+架构系列的MCU举例。这里的零地址开始的一块连续的存储块,位于Flash闪存区。并且这块存储块存储的是系统的一张中断向量表。该零地址单元存储的是MSP初始值(主存栈顶指针)。将野指针默认指向零地址后,如果后续未赋值而企图操作该野指针,那么逻辑上就是对该零地址进行操作。但是从物理上来说,对Flash中单元的擦写需要遵循一定的时序逻辑,只是简单通过指针访问并不能改变该值,所以这维护了对未初始化指针操作安全性。
总结:关于指针和数组的内存分配,需要考虑的是它们本身元素数据类型和指针数据类型的问题。关于指针和数组的初始化,需要考虑的是它们是静态存储类型还是自动存储类型,不同类型对应编译器自动初始化操作不同。