C语言指针详解


title: 指针详解
date: 2023-11-23 07:35:39
tags:

原文见我的网站: www.supdriver.top

这篇质量不太行:(

内存和地址

在了解指针之前,先讲讲内存是如何管理的

首先因为内存很大(一般有几个G),所以为了高效管理,有了内存单元的概念。而这个单元的大小,正好是一个字节。

因为一个比特位就是一个二进制位,太小了,超过一个字节,在处理char这样一个字节长的变量很麻烦。

定下长度后,就可以给内存单元编号,而每个内存单元获得的独一无二的编号,便是它的地址,以声明了一个变量a为例,示意图如下

变量的地址

上图中a占4个字节,每个字节都有自己的地址,但要找到a其实只需要找到第一个地址就行了,实际上在C语言中也是如此,a的地址就是首字节地址,即图中的0x000000AF88DFF6A4

关于几个名词

C语言中称地址指针,储存地址的变量叫指针变量,平时也简称指针,此时强调的是指针变量里储存的地址,而不是这个变量。

指针变量的组成

指针变量也要拆成两部分来看

一个是变量的,在同一个程序中,所有指针变量的值的长度都是一样的,都指向了某个内存中的字节, 至于具体多长,取决于环境:32位程序是4个字节,64位程序是8个字节

另一个是变量的类型,类型决定编译器从所指向的字节,向后总共读几个字节,以及用什么方式读取内存里的内容。以下图的代码为例

可以看到三种指针指向了同一个字节,即它们的值是相等的,但指针类型不同,解引用之后得到的也不同,

charint短,所以*p_char只能取到00,

而虽然floatint一样长,但对内存的读法不同,所以*p_float*p_int依然不同

指针(变量)的使用

声明指针变量

指针变量也是变量,在没有结合性问题时,和一般变量的声明方式差不多。

变量的声明:变量类型 + 变量名

指针的声明:指向的变量类型 + * + 变量名

以下以声明一个字符指针为例

char* pointer = NULL;

变量的声明逻辑如上图

进阶:二级指针->N级指针

我们可以用同样的逻辑声明更高级的指针

char* *ppstr = NULL;//ppstr是一个二级指针
char** *ppstr = NULL;//pppstr是一个三级指针

在声明中,前面的char*声明了ppstr指向的变量类型,后面的*变量名结合,声明ppstr是一个指针.

此处,称指向一级指针的指针为二级指针,同理有三级指针,至N级指针.

指针的解引用

指针最常见的用处就是通过变量里储存的地址,通过直接修改目标变量的内存来修改变量的值 , 当然还有强制转换指针类型来读取目标变量的一部分内存 之类的骚操作

函数的传址调用

在遇到指针前,使用函数时,由于实参传到函数里都变成了形参,无法通过形参(包括修改形参的值)来改变实参的值,因为形参终究只是实参的一份临时拷贝.

而有了指针之后,函数的实参,形参关系不变,但我们有了更高端的形参,也就是指针, 尽管函数内的指针依旧是函数外的指针临时拷贝,但我们已经能通过其储存的访问函数外变量的内存了,同时包括读取修改, 这种通过传入指针来修改外部变量的函数调用,便称为函数的传址调用

以如下代码为例

void Swap_int(int*a,int*b)
{
    //给我两个整型的地址,我就能 真·交换它们的值
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
提问?如何修改函数外的指针的值?

依然还是把这一指针的地址传进去,而函数的形参写成更高一级的指针

如下代码,例如我想在函数里把外部的指针置空

void Reset(char* *pstr) 
{
    *pstr = NULL;
}

int main()
{
    char* str;
    Reset(&str)//对一级指针取地址,传入二级指针
    return 0;
}

有关指针的危险操作

野指针的解引用

有些指针因为错误操作,指向了不能访问的内存,一旦解引用,就有可能使程序崩溃

情形如下

使用了 未初始化/赋值 的指针

int* pa;//未初始化,pa的值为随机值
*pa = 0 ;//野指针的解引用

//正确的用法
int arr[10] = {0};

int* pa;
pa = arr;//立即初始化
int* pb = NULL;//初始化

所以声明指针时最好初始化,如果不知道初始化成什么,就用NULL空指针初始化

指向了 已回收的 内存空间

有的函数错误地返回了内部临时变量的地址, 在外面使用返回的指针,因为此时函数的栈帧已经销毁*,会发生野指针的解引用

char* fun(void)
{
    char a = 0;
    char* pa = &a;
    return pa;
}

int main()
{
    //情形一
    char* pa = fun();
    *pa = 1;//此时变量a已经销毁,发生野指针的解引用,即非法访问

    //情形二
    char* str = (char*) malloc(sizeof(char) *10) //在堆区开辟10个字节的空间
    free(str);//然后释放掉
    str[0] = 'A';//试图访问已free的内存,并写入,发生非法访问
    return 0;
}

空指针的解引用

空指针NULL,0,一旦解引用就会报错,所以在解引用陌生指针时一定要注意判空

//情形一
char* func(char* str)
{
    if(str == NULL)//防止使用者错误传入空指针
    {
        return NULL;
    }

    printf("%s\n");
}
//情形二
char* InitArray(char** pstr)
{
    if(pstr == NULL)
    {
        return NULL;
    }
    *pstr = (char*)malloc(sizeof(char) * 10);
    if(*ptr == NULL)//malloc一旦失败就会返回NULL,所以调用后一定要判空
    {
        return NULL;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值