C 语言中的指针类型


2021年8月23日, Learn eTutorial
1845

在本教程中,您将了解 C 语言中常见的指针类型及其语法和用途。此外,您还将了解在使用这些指针时可能出现的一些问题,并通过简单的示例学习如何解决它们。

空指针

在学习编程中的空指针之前,让我们先从计算机内存的上下文中理解空指针。在内存中,空指针是一个简单的命令,用于指示程序或操作系统指向一个空位置。 

C 语言中的空指针是一类特殊的指针,它们不指向任何特定的内存地址。对于空指针,我们设置一个空值而不是内存地址。下图显示了一个带有空指针的链表。

NULL POINTERS

空指针的语法是:


data_type * pointer_variable = NULL;
 

在编程语言中,'NULL' 关键字是为此目的特别保留的。对于不同的数据类型,它们的声明方式如下:


int *pntr = NULL;
char *pntr = ‘\0’;
float *pntr = (float *)0;

下面的程序将给出 C 语言中空指针的概念。


#include<stdio.h>
int main()
{
 int* vp = NULL;

  printf("vp contains value:%d\n",vp);
}

输出

vp contains value:0

与 void 指针不同,空指针是类型指针,因为它们指定了指针变量的数据类型,但值为“空”。因此,它总是值为0。在程序中,vp 是一个整数类型的空指针,其值为 0。

为什么使用空指针?

  1. 空指针最主要和首要的用途是初始化指针变量。 

    通常,当像下面这样声明一个指针变量但未初始化时,

    
    int * p; // uninitialized pointer
     
    

    实际上发生的是,它将指向某个随机内存地址并存储一些垃圾值。当您尝试使用此指针或将其作为参数传递给函数时,此垃圾值可能导致程序崩溃。为避免这种情况,请始终使用空值初始化指针,如下所示。

    
    int * p = NULL; // null pointer
     
    
  2. 其次,作为参数传递空指针给函数。

    如果您不想将有效的内存地址传递给函数,可以使用空指针作为其参数。

    
    float funct(int *p)
    {
    ::::::::::::
    }
    funct(NULL);
     
    
  3. 用空值验证指针

    在访问指针之前,请务必确保指针变量已初始化为有效的内存地址或空值。否则,可能会出现意外错误,造成麻烦。

    
    #include<stdio.h>
     
    void sum(int *p2)
    {
        if(p2 == NULL)
        {
            //Handle NULL pointer
            return;
        }
        else
        {
            //function body
        }
    }
    void main()
    {
        int *p1 = NULL;
        sum(p1);
    }
     
    

    在上面的代码中,p1 是一个空指针,它被作为参数传递给函数 sum。该函数最初会检查传递的参数是否为空指针。如果它是空指针,则代码将处理该空指针。在其他条件下,将执行函数体。

  4. 为了避免在取消分配时出现悬空指针的情况,可以使用空指针。

    考虑一种情况,您有一个指针,它存储了变量的内存地址并包含数据。如果您希望删除数据以释放内存,那么指针会发生什么?即使在删除数据后,指针仍会保持不变,并指向相同的内存位置。这些类型的指针称为悬空指针。为了规避这种情况,最佳解决方案是将指针赋值为 NULL。

    
    #include<stdio.h>
     
    void main()
    {
        int *p = (int *)malloc(SIZE);
        //. . . . . .
        //. . . . . .
     
        free(p);
        //pointer p is now a dangling pointer
     
        p=NULL;
        //Now p is a null pointer not a dangling pointer
    }
    
    

    上面的代码片段说明,在执行 free() 函数后,指针中的数据被释放,指针变成了一个悬空指针。当指针设置为 NULL 值时,它会变成一个空指针。在处理链表和树等数据结构时,您可以看到这一点。

VOID 指针

在我们之前的教程中,我们了解到指针的数据类型必须与存储其地址的变量的数据类型相对应。例如,整数指针必须指向整数变量。但是,当程序员事先不知道变量类型时会发生什么?

在这种情况下,void 指针非常有用。在 C 编程中,void 指针也称为通用指针,它可以指向任何数据类型的变量,尽管它没有标准的。数据类型。关键字 void 用于创建void指针。 void 指针可以存储任何变量的地址,而不管其数据类型如何。

void 指针的语法是: :


void * pointer_variable ;
 

下面是 void 指针的示例程序;


#include <stdio.h>
int main()
{
  int x = 10;
  char c = 'C';
  void* vp;

  vp = &x;
  printf("vp stores address of integer variable x:%x\n",vp);
  printf("size of void pointer is : %d\n",sizeof(vp));

  vp = &c;
  printf("vp stores address of character variable c:%x\n",vp);
  printf("size of void pointer is : %d",sizeof(vp));

}

 

输出


vp stores address of integer variable x:61fe14
size of void pointer is : 8

vp stores address of character variable c:61fe13
size of void pointer is : 8

当您查看上面的代码时,我们有两个变量:整数类型的 x 和字符类型的 c。vp 是 void 类型的指针变量。因此,vp 能够存储变量的地址,而不管其数据类型如何。最初,vp 存储了整数变量 x 的地址,稍后又存储了变量 c 的地址。因此,vp 实现了可重用性。

解引用 void 指针

在我们之前的教程 C 语言中的指针 中,我们讨论了指针的解引用。现在让我们看看是否可以解引用 void 指针。观察下面的示例。


#include<stdio.h>
void main()
{
int x = 200;
void* vp ;

vp = &x;
printf("%d", *vp);

}

输出


Invalid use of void expression

#include<stdio.h>
void main()
{
int x = 200;
int* p ;

p = &x;
printf("%d", *p);

}

 

输出


200

上面的 2 个代码片段是对 void 指针和典型指针解引用的比较。从比较中,我们可以理解 void 指针不能像典型指针那样解引用。如以下所示,在解引用之前,必须将 void 指针转换为适当的数据类型。


#include<stdio.h>
void main()
{
int x = 200;
void* vp ;

vp = &x;
printf("%d", *(int *)vp); //type casting

输出

200

此处,

  • (int *) 执行类型转换,其中 void 指针 vp 被暂时转换为整数指针,并且类型转换的生命周期在表达式求值完成时结束。
  • *(int *) 对类型转换后的指针进行解引用。

void 指针的指针算术

C 不支持 void 指针的指针算术。其原因是 void 不是真正的类型,因此 sizeof(void) 没有 proper 的含义。由于指针算术会按 sizeof 指向对象倍数更改指针值,因此需要有意义的大小。这里,void 无法提供正确的大小,因此不适合指针算术。

野指针

在学习空指针时,我们遇到了未初始化的指针,这些指针指向某些任意位置,并导致程序行为错误或崩溃。这种未初始化的指针在 C 语言中被称为野指针。


int * p; // Wild pointer
 

要将野指针转换为指针,我们需要在使用它们之前初始化它们。可以通过以下示例中的两种方式完成。


#include<stdio.h>

#include

int main()

{

    int * ptr; //wild pointer
    int
    var;

    // Method 1
    var = 100;
    ptr = &
        var; // Now ptr is no longer a wild pointer     printf("\n ptr c *(ptr));

    //Method 2 -creating memory allocation dynamically
    int * p = (int * ) malloc(sizeof(int));
    * p = 100;     printf("\n p c *(p));
    return 0;

}
 

这里,ptr 和 p 是在使用前初始化的两个指针变量。最初,ptr 是一个野指针,之后初始化将其变为普通指针,因为它指向内存位置。避免野指针的另一种方法是使用 calloc、malloc 或 realloc 动态分配指针。

悬空指针

“悬空”一词的意思是“松散地悬挂”,我们知道指针是对内存位置的引用。因此,当指针指向无效或未保留的内存位置时,它被称为悬空指针。更准确地说,它是程序执行期间某个时刻有效的,但当前未指向对象的指针。

悬空指针在对象销毁时出现,特别是当对象被删除或从内存中释放,而没有修改指针的值,导致指针仍然引用已删除的原始内存位置时。

NULL POINTERS

上图显示指针 A 和指针 B 分别指向已分配对象 A 和 B 的内存。另一方面,指针 C 指向已删除对象的内存,因此它被称为悬空指针。

使用悬空指针可能导致多种不同类型的问题,例如:

  • 不可预测的行为 
  • 段错误 / 常规保护错误
  • 由于无声损坏无关数据而导致的错误

悬空指针的原因

1.    释放或 free 变量内存


#include<stdio.h>

#include

int main()

{

    int * ptr;

    //creating memory allocation dynamically
    ptr = (int * ) malloc(sizeof(int));
    printf("\n Memory allocated...");
    * ptr = 100;     printf("\n ptr c *(ptr));

    //Now ptr becomes a dangling pointer
    free(ptr);
    printf("\n Memory is freed ...\n ");     printf("ptr c *(ptr));

    //Now ptr is no longer a dangling pointer
    ptr = NULL

    return 0;

}
 

输出


 Memory allocated...  ptr c
 
 Memory is freed  ptr c

在此示例中,在执行 free() 函数后,ptr 的内存将被释放,因此成为悬空指针。

2.    变量超出作用域


#include<stdio.h>

int main()
{
    char **StrPtr;
    {
        char *StrVar = "Hai";
        StrPtr = &StrVar;
    }
    // Since StrVar falls out of scope StrPtr is now a dangling pointer

    printf("%s", *StrPtr);
}

 

在此示例中,我们最初创建了一个指针变量 StrPtr。然后我们创建了另一个变量 StrVar,它的可见性被限制在局部块内,因此在外部块中不可见。包含 StrVar 地址的 StrPtr 在超出内部块后成为悬空指针,因为 StrPtr 仍然指向外部块中的无效内存位置。

3.    在函数调用中返回局部变量


 #include <stdio.h>

 int * func() {
     int l = 10;
     return &l;
 }
 int main() {
     int * p = func();
     printf("%d", * p);
     return 0;
 }
 

在此示例中,我们首先创建了一个指针变量,该变量存储 func() 的返回值。当调用 func() 时,将返回局部变量“l”的值。但是当它进入 main 函数时,l 的值不再可见,因此 p 成为悬空指针。

如何避免悬空指针的发生

要避免悬空指针的发生,请在释放或取消分配相应指针后,将其初始化为 NULL 值。我们在空指针部分已经介绍过这一点。