c语言中的各种指针(空指针、野指针、悬垂指针、void指针)

首先声明:这里说的概念都是c语言中的,跟c++会有些许不一样。

预备知识

1.空指针常量(null pointer constant)

An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.

这里即是说明:值为0的整型常量表达式,或强制(转换)为 void * 类型的此类表达式,称为 空指针常量 。
如0、0L、3-3、'\0'、017、(void)0等都属于空指针常量。
至于系统会采用哪种形式来作为空指针常量使用,则是和具体实现相关。一般的C系统采用 (void *)0 或者 0 的居多,也有个别采用的 0L ;至于C++系统,由于存在严格的类型转换要求, void * 不能像C中自由转换成其他指针类型,所以通常选择 0 作为空指针常量。

把空指针常量赋给指针类型的变量p,p就成为了一个空指针

2.NULL值

The macro NULL is defined in <stddef.h> (and other headers) as a null pointer constant.

即NULL是一个标准规定的宏定义,用来表示空指针常量。
我们找到 stddef.h 中的该宏定义:

#define NULL ((void *)0)

毫无疑问,NULL就是一种空指针常量。
那有个问题,我们可以自定义NULL值吗?
实际上NULL是标准库中的一个 reserved identifier (保留标识符) ,所以如果包含了相应的标准头文件引入了NULL的话,再在程序中重新定义NULL为其他值(比如把NULL定义为3)就是非法的。

一、空指针(null pointer)

1.空指针定义

If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.

通过预备知识中对于空指针常量NULL值的讲解,我们可以知道:
只要将空指针常量赋给指针类型变量,该指针变量就是空指针。

int *p;
p = 0;
p = 0L;
p = '\0';
p = 3 - 3;
p = 0 * 17;
p = (void*)0;
p = NULL;

如上所示代码,经过其中任何一种赋值操作后,p就是一个空指针。而且,由系统保证空指针不指向任何实际的对象或函数。反过来说就是:任何对象或者函数的地址都不可能是空指针。

2.空指针的内存指向

标准并没有对空指针指向内存中什么地方这一问题做出规定。也就是说,具体使用 0x0地址 还是其他地址来表示空指针,都依赖于具体系统的实现。两种实现如下:
(1)零空指针(zero null pointer)
这是我们常见的一种实现,即空指针的内部用全0来表示。
(2)非零空指针(nonzero null pointer)
也有一些系统用一些特殊的地址值或者特殊的方式来表示空指针。

我们在实际编程中不需要了解我们系统空指针的实现和内存指向,我们只需要了解一个指针是否是空指针就可以了——编译器会自动实现其中的转换,为我们屏蔽掉其中的实现细节。

3.空指针的使用

空指针的使用,主要就是防止野指针和防止悬垂指针。
防止野指针

int *p = NULL;

防止悬垂指针

int *p = malloc(sizeof(int));
free(p);
p = NULL;  // 置空

详细见下文野指针和悬垂指针。

二、野指针(wild pointer)

1.野指针概念

野指针:没有初始化的指针

2.野指针产生原因

指针变量未初始化

如下程序:

int main()
{
    int *p;
    printf(%p", p);
    return 0;
}

这里的p未被初始化,它的缺省值是随机的。
因此我们在声明一个指针变量的时候,为了防止出现野指针的问题,可以将其初始化为NULL,即设为空指针;也可以咋初始化时就将指针确定指向。
如下所示:

int a = 3;
int *p = &a;
// 或者
int *p = NULL;
int *p = 0;

其中int *p = 0和int *p = NULL都比较常用。

三、悬垂指针(dangling pointer)

1.悬垂指针概念

悬垂指针:指向已经被释放的自由区内存(free store)的指针。
它和野指针的区别就在于:悬垂指针曾经有效过,现在失效了;但是野指针从未有效过。

2.悬垂指针产生原因

(1)指针指向的内存释放之后未置空

指针指向的内存被free或者delete释放后,指针的值仍然为刚刚被释放的那块内存的首地址,但是此时指针已经失去了对那块内存的合法访问权限。
如下程序所示:

int main()
{
    int *p = NULL;
    p = malloc(sizeof(int));
    *p = 3;
    printf("Before free, p = %p, *p = %d\n", p, *p);
    free(p);
    /* 注意,此时p和p指向的内容并没有发生变化,但是free内存后已经失去了
对堆上那块内存的合法操作性 */
    printf("After free, p = %p, *p = %d\n", p, *p);
    
    return 0;
}

程序输出:

Before free, p = 0xe7a010, *p = 3
After free, p = 0xe7a010, *p = 0

在程序执行free(p)之后,p就是一个野指针。为了避免野指针可能引起的问题,我们应该在free(p)之后加上:

p = NULL;

这个操作就称为置空

(2)指向同一块内存多个指针之一被释放

这种情况严格来讲跟第一种情况是一回事儿,示例代码如下:

int *p = malloc(sizeof(int));
*p = 3;
int *pd = p;
/* 当前p和pd指向的是同一块内存 */
free(p);
p = NULL;
/* 释放掉p所指向的内存,并将p置0 */

从上述代码可以看出,若有两个指针指向同一个内存,其中一个指针被free且被置空后,另一个指针却仍然指向那块被释放了的内存空间,这就成了一个悬垂指针。
这是悬垂指针产生的一个典型示例,常见于实际生产环境中。

(3)指针操作超出变量生命周期

不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
示例代码如下:

// 定义一个函数
char *getString()
{
    char *p = "Hello World!";
    return p;
}

在函数内部定义的字符串指针p,其指向的内容存放在栈上,当这个函数执行完后退出后,这部分空间就会被释放,返回过去的p指针就成了悬垂指针。

四、void指针(void pointer)

1.void指针概念

void的意思是“无类型”,所以void指针又被称为“无类型指针”,void指针可以指向任何类型的数据,所以void指针一般被称为通用指针或者泛指针,也被叫做万能指针

2.void指针的使用

(1)void指针变量p所指向的内容不能通过*p去访问

如果要通过void指针去获取它所指向的变量值时候,需要先将void指针强制类型转换成和变量名类型想匹配的数据类型指针后再进行操作,如下所示:

int a = 5;
void *p = &a;
printf("*p=%d\n", *p);

编译器会报错,提示你:

incomplete type is not allowed.

意思就是这个类型不完整,没有办法去获取它所指向的变量值。改成:

printf("*p = %d\n", *(int *)p);

就可以了。

(2)void指针赋给其他类型的指针

一个常见的使用场景就是:动态内存申请与释放
首先要明确一件事,c语言和c++在一些语法实现上有区别,这里我们说的是c语言。

C语言中,void指针赋给其他任意类型的指针(除开函数指针,void指针赋给函数指针下面讨论),是天经地义的,无需手动强转;其他任意类型的指针赋给void指针,也是天经地义的。

例如:

typedef struct {
    ...
    ...
} complex_struct;   
// c语言中的正确写法:
complex_struct = malloc(sizeof(complex_struct));
// c语言中多此一举的写法:
// complex_struct = (complex_struct)malloc(sizeof(complex_struct));

如果你发现不手动强转,编译器出现warning,请注意一下自己是不是忘了加malloc函数的头文件:

#include <stdlib.h>

因为在c语言中,编译器会把任何未定义的函数默认返回int,因此如果我们不加stdlib.h这个头文件,malloc函数默认返回int,所以我们看到编译器警告,如果看得不仔细的话会以为是因为没有强制类型转换导致两边类型不匹配,其实编译器提示的是int *和int类型不匹配,而不是int *和void *类型不匹配!
编译器警告

(3)void指针赋给函数指针

void指针可以赋给函数指针以外的其他所有指针,malloc函数就是一个例子。至于函数指针的问题,这里来讨论。
linux中有个dlsym函数,函数声明如下:

void *dlsym(void *handle, const char *symbol);

man手册中有个示例代码:

int main()
{
    ...
    void *handle;
    double (*cosine)(double);
    ...
    *(void **) (&cosine) = dlsym(handle, "cos");
    ...
}

这里面它想用函数指针去接收dlsym函数返回的void指针,却没有用以下两种更自然的、好理解的形式:

cosine = dlsym(handle, "cos");
cosine = (double (*)(double)) dlsym(handle, "cos");

这是因为C99标准的遗漏,导致了void指针无法直接转换成函数指针。所以它用了下面这种夸张的转换:

*(void **) (&cosine) = dlsym(handle, "cos");

先把consine取地址变成二级指针,然后将这个强转成(void **)这个void二级指针,再经过指针运算符*变成void一级指针,这样左右两边类型就匹配了。

(4)其他类型的指针赋给void指针

void指针可以用作泛型,接收任意数据类型指针。
void指针用于指向特定地址,而无需关心这个地址上存放着什么类型的数据。例如常见的memcpy等函数就用到void*,函数原型如下:

void *memcpy(void *des, void *src, size_t n);

此处的void *des和void *src可以接收任意类型的数据类型指针,既然是内存拷贝,入参就不应该限制传入什么类型的指针,逻辑上十分合理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容