以下内容为他人文章的复制粘贴,供参考,原文出处C语言中指针、二级指针和野指针问题 - 简书
野指针与空指针
空指针:
void* 类型的指针,可以指向任何类型对象的地址。但是在取空类型指针所指向的值的时候,应将空类型指针转换为对应的指针类型。
void *pa = &a;
int aa = (int)(*pa); //错误
修改:
int aa = *((int*)pa);
里面的括号(int*)表示将空类型指针pa强转为int型指针,外面这个括号还外加一个 * 号,表示取该int型指针所指向的值。
1)空指针支持的操作
a. 与另一个指针比较
b. 向函数传递void *指针
c. 在函数里返回void *指针
d. 给另一个void *指针赋值
2)空指针不支持的操作
a. 不支持解引用,不能直接获取指向对象的值
b. 不能进行指针运算,比如移位操作(因其不知道下移一位需要开辟多大的空间)
野指针:
指针未指明确切的地址,造成随机指向系统的内存。
出现原因:
1.指针在定义的时候未初始化,也没有定义为NULL;
2.指针在被free或者delete时没有将原指针设置为NULL;(free或者delete只是将内存空间释放,指针的值并没有被释放,还是指向同样的位置。)
上面这一段需要注意,定义一个指针变量,动态开辟内存将首地址赋值给这个指针变量,实际上是完成了两个操作,一个是建立了一个指针变量,另一个是在内存中开辟了一个空间(这是两个独立的动作。free(指针)的时候,只是free了指针指向的内存空间,但是指针变量的值仍然是指向那块地址,是把那块地址对应的内存空间上的内容清掉了,但是指针变量的值还是指向那块地址,因为那块地址已经不再被使用了,为了避免后面误引用,要把指针变量也赋值为NULL,这样后面如果误引用的话,程序会报Segment fault段错误,提醒错误的引用了指针。)
项目代码中的野指针实例
项目背景:项目是应用开源的一个编解码库,这个库里定义了很多多层嵌套的结构体,在这样的结构体中无可避免地涉及到很多一级指针和二级指针,例如:
一级指针:
typedef struct BasicSafetyMessage {
MsgCount_t msgCnt;
OCTET_STRING_t id;
DSecond_t secMark;
TimeConfidence_t *timeConfidence /* OPTIONAL */;
Position3D_t pos;
struct PositionalAccuracy *posAccuracy /* OPTIONAL */;
struct PositionConfidenceSet *posConfidence /* OPTIONAL */;
TransmissionState_t transmission;
Speed_t speed;
Heading_t heading;
SteeringWheelAngle_t *angle /* OPTIONAL */;
struct MotionConfidenceSet *motionCfd /* OPTIONAL */;
AccelerationSet4Way_t accelSet;
BrakeSystemStatus_t brakes;
VehicleSize_t size;
VehicleClassification_t vehicleClass;
struct VehicleSafetyExtensions *safetyExt /* OPTIONAL */;
struct VehicleEmergencyExtensions *emergencyExt /* OPTIONAL */;
/*
* This type is extensible,
* possible extensions are below.
*/
/* Context for parsing across buffer boundaries */
asn_struct_ctx_t _asn_ctx;
} BasicSafetyMessage_t;
也有这样的涉及二级指针的结构:
struct \
{ \
type **array; \
int count; /* Meaningful size */ \
int size; /* Allocated size */ \
void (*free)(type *); \
}
由于对指针部分的学习不扎实,在撸代码的时候遇到很多野指针的情况,导致程序报错崩溃,或运行结果出错。下面列出几个比较典型的开发过程中踩到的坑。
1)二级指针的操作
rsm_point.participants.list.array
这里的array就是上面那个struct中定义的一个二级指针,这个二级指针是一个指向结构体指针的指针,其用途是存储一连串的结构体。即在这个指针下面存储这一组结构体。在使用的时候需要类似一级指针那样在前边加 * ,只不过这里的二级指针要在前边加 **,像这样:
(**(rsm_point.participants.list.array)).accelSet->lat
或者这样:
(*(rsm_point.participants.list.array[0])).accelSet->lat
第二种方法个人感觉更清晰也更容易理解二级指针的意义。并且要注意把二级指针整体用括号括起来才能对其所指的结构体里对象进行后续操作,否则就会导致在使用了"·"之后不出现结构体内定义对象提示的情况。
**(rsm_point.participants.list.array). //..一般在使用了"·"之后会自动弹出指针所指结构体对象,但此处由于没有将二级指针括起来,"·"之后不会出现任何提示
2)二级指针的初始化
上面提到array是一个指向结构体指针的指针,所以在初始化的时候,需要先定义一个结构体A,并定义一个指针指向这个结构体A_ptr,而array则是一个指向A_ptr的二级指针,定义过程如下:
Demo_BSM_t bs_msgFrame; //..定义结构体
Demo_BSM_t *bsm_ptrr = &bs_msgFrame; //..定义结构体指针
global_variable_message->Bsm_list.array = &bsm_ptrr; //..给array赋初值
这样就完成了二级指针的初始化。此处要强调的并不是这个二级指针的定义过程,而是要注意每一级指针的类型和所指对象的类型要一致,指针所指对象的类型会影响到指针移位操作时系统为移位操作所开辟的空间和这个指针所指向的实际地址。这也就引出我下面的操作,指针的移位和回退。
3)指针的移位和回退所引发的野指针
伪代码:
1 int move_num_flag = 0; //..用来记录指针后移的次数
2 for (int i = 0; i < global_variable_message->Bsm_list.count; i++) //..每收到一个ID就遍历列表中的所有ID,如果存在相同ID则不进行处理,count表示列表中结构体的个数
3 {
4 if(ID相同)
5 {
6 break;
7 }
8 else
9 {
10 move_num_flag += 1;
11 **(global_variable_message->Bsm_list.array)++; //..指针下移一位,继续对比下一个结构体的ID是否与本次ID相同,直到遍历完毕。
12 }
13 }
14
15 if(move_num_flag == global_variable_message->Bsm_list.count) //..此处为真说明遍历完整个队列没有发现相同ID,将新ID的结构体内的对象值赋值给array下的新结构体对象。
16 {
17 (**(global_variable_message->Bsm_list.array)).demo_vehicle_id.size == new_msgFrame->choice.bsmFrame.id.size;
18 }
19
20 for(int i = 0; i <move_num_flag; i++)
21 {
22 **(global_variable_message->Bsm_list.array)++; //..指针回退到首位
23 }
这段代码的逻辑其实很简单,收到一条新结构体信息,移动指针array对比新结构体的ID是否已经在array中,如果有则退出,没有的话插入到array所指列表的末尾,并将array指针回退到首地址。
但这里有存在两处错误:
错误1:代码11行,若array列表中不存在这个新结构的ID,这个循环就会执行count次,而最后一次array所指的位置为整个array列表之后的一个地址,简言之就是超出了已赋值array地址,造成此时array所指位置不明确而产生野指针。
错误2:代码17行,由于此时array的地址已经超出了其最大地址,且随机指向内存,此时再对array所指结构体下的内容进行赋值的时候,
对于结构体类型的二级指针:可以看下面这个图
比如有一个结构体叫做Node,那么Node*可以认为是指向Node这个结构体数组的指针,那么Node**就是(Node*)*,也就是一个指向结构体指针数组的指针变量,也就是结构体二级指针。