七、C语言指针

七、C语言指针

指针是 C 语言赋予程序员的上帝之手,它允许我们直接操作内存。用好了,它是神兵利器;用不好,它是程序崩溃的根源。这次将带你深入内存,理解指针的本质。

思维导图

在这里插入图片描述


在这里插入图片描述

一、 指针基础概念

1.1 什么是地址?

计算机内存就像一个巨大的公寓楼,每个字节都有一个唯一的门牌号,这就是地址
变量名只是门牌号的别名,指针则是专门用来存放门牌号的变量

1.2 语法结构:定义与赋值

语法:类型 *指针变量名;

int a =10;int*p;// 1. 定义:p 是一个指向 int 的指针 p =&a;// 2. 赋值:把 a 的地址给 p(p 指向 a)int*q =&a;// 3. 定义并初始化(推荐写法)

1.3 两个核心运算符

& (取地址):获取变量在内存中的位置。&a 得到的是一个十六进制的地址值。
* (解引用/间接访问)拿着钥匙开门。访问指针指向的那个位置里的数据。

代码示例:用指针修改变量

int a =10;int*p =&a;// p 存储了 a 的地址printf("a 的地址: %p\n",(void*)p);printf("通过指针看 a: %d\n",*p);*p =20;// 核心操作!远程修改 a 的值printf("a 现在是: %d\n", a);// 输出 20

1.4 指针类型决定视野

为什么指针需要类型?int *char * 存的都是地址(在 64 位系统上都是 8 字节),区别在哪?
区别在于解引用时的视野

int *p: *p 一次性读取4 个字节。
char *q: *q 一次性读取1 个字节。

代码验证:

int x =0x12345678;char*p =(char*)&x;printf("%x",*p);// 在小端序机器上,输出 78 (只读了最低字节)

二、 致命三剑客:NULL、野指针、悬空指针

2.1 NULL 指针

NULL 是一个宏,通常定义为 (void*)0不指向任何有效内存。
最佳实践: 定义指针时如果暂时不用,务必初始化为 NULL

2.2 野指针

定义了指针但没初始化。它指向哪里?只有上帝知道(垃圾值)。

int*p;// 野指针!// *p = 100; // 极度危险!可能覆盖关键系统数据导致崩溃

2.3 悬空指针

指针指向的内存已经被释放,但指针还在。这是最难调试的 Bug 之一。

经典错误:返回局部变量地址

int*get_val(){int x =10;return&x;// 错误!函数结束 x 就销毁了}intmain(){int*p =get_val();// p 变成了悬空指针// printf("%d", *p); // 未定义行为,数据可能已经被覆盖}

三、 指针实战:交换两个数

这是理解地址传递最经典的代码。

代码示例:

// 接收地址,拥有“远程修改”的能力voidswap(int*a,int*b){int temp =*a;// 取出 a 指向的值*a =*b;// 把 b 指向的值赋给 a 指向的地方*b = temp;// 把 temp 赋给 b 指向的地方}intmain(){int x =5, y =10;swap(&x,&y);// 必须传地址!printf("x=%d, y=%d\n", x, y);// 输出 x=10, y=5return0;}

四、 指针与数组

4.1 数组名的真面目

在表达式中,数组名 arr退化为指向首元素的指针。

核心等价公式:
arr[i] ≡ *(arr + i)

4.2 用指针遍历数组

这种写法在嵌入式开发和操作系统内核中非常常见,效率极高。

代码示例:

int arr[]={10,20,30};int*p = arr;// 指向开头for(int i =0; i <3; i++){// 写法 1:下标法(最直观)printf("%d ", arr[i]);// 写法 2:指针算术法(偏移)printf("%d ",*(p + i));// 写法 3:指针移动法(步进)printf("%d ",*p++);// 取值后,指针指向下一个}

五、 指针算术与边界

5.1 +1 到底加多少?

指针加 1,不是地址值加 1,而是跳过一个数据类型的大小

char *p: p+1 地址增加1 字节。
int *p: p+1 地址增加4 字节。
double *p: p+1 地址增加8 字节。

代码验证步长:

int nums[]={1,2};int*p = nums;printf("当前: %p\n",(void*)p); p++;printf("加一: %p\n",(void*)p);// 观察地址差值,一定是 4

5.2 两个指针相减

如果两个指针指向同一个数组,p1 - p2 得到的是它们之间相隔的元素个数(不是字节数)。这是实现 strlen 的基础。

手写 strlen (指针版):

size_tmy_strlen(constchar*s){constchar*start = s;while(*s !='\0'){ s++;// 指针一直往后走}return s - start;// 尾地址 - 头地址 = 长度}

六、 const 与指针(绕口令时间)

这是面试必考题,记住这句口诀:

const 在 * 左边,锁的是物(内容)
const 在 * 右边,锁的是人(指针)

6.1 三种形态对比

int x =10;int y =20;// 1. 指向常量的指针 (Pointer to Const)// "我不能改里面的值,但我可以指向别人"constint*p1 =&x;// *p1 = 30; // 错!内容被锁 p1 =&y;// 对!指针没锁// 2. 常量指针 (Const Pointer)// "我可以改里面的值,但我不能指向别人"int*const p2 =&x;*p2 =30;// 对!内容没锁// p2 = &y; // 错!指针被锁// 3. 指向常量的常量指针 (双重锁定)// "我都不能改"constint*const p3 =&x;

七、 练习题

题目 1: 64 位系统下,int *pchar *q 占用的内存大小分别是多少?

题目 2:int a = 10; int *p = &a;*&a 的结果是什么?&*p 的结果是什么?

题目 3: 为什么指针被释放(free)后,最好立即置为 NULL

题目 4:short *p = 0x1000; (short占2字节),p + 2 的地址值是多少?

题目 5:int arr[5]; int *p = arr;,表达式 p[2] 合法吗?它等价于什么?

题目 6: 下面代码输出什么?

int a[]={1,2,3,4};int*p = a;printf("%d",*p++);printf("%d",*p);

题目 7: 解析声明:const char * pchar * const p 的区别。

题目 8: 如何定义一个指针,指向一个 int 类型的指针(二级指针)?

题目 9: 只有 void* 指针可以直接赋值给其他类型的指针而不需要强转(在 C 中),对吗?

题目 10: 下面代码有什么致命问题?

int*p;*p =100;

题目 11: 两个指针相加(p1 + p2)有意义吗?两个指针相减(p1 - p2)有意义吗?

题目 12: 在大端序(Big Endian)机器上,int a = 0x12345678; char *p = (char*)&a;*p 的值是多少?

题目 13: 什么是“野指针”?与“悬空指针”有何区别?

题目 14: 函数参数 void func(const int *p) 里的 const 想表达什么意图?

题目 15:int a[3][4]; int *p = a; 这种赋值会报警告吗?为什么?

八、 解析

题 1 解析
答案:
都是 8 字节。
详解:

指针也是变量,用来存地址。在 64 位系统上,地址总线宽度是 64 位,所以所有类型的指针大小都是 8 字节。

题 2 解析
答案:
10a 的地址
详解:

*& 互为逆运算,相互抵消。

题 3 解析
答案:
防止悬空指针(Dangling Pointer)误用。
详解:

释放内存后,指针变量里的地址值还在。如果不置空,后续代码可能会误以为这块内存还能用,导致难以排查的 Bug。

题 4 解析
答案:
0x1004
详解:

步长 = sizeof(short) = 2。0x1000 + 2 * 2 = 0x1004

题 5 解析
答案:
合法。等价于 arr[2]*(p+2)
详解:

指针和数组名在下标访问上是语法糖关系。[] 运算符本质上就是指针加法后解引用。

题 6 解析
答案:
12。
详解:

*p++ 先取值 *p (1),然后 p 自增指向下一个。第二次打印 *p 就是 2。

题 7 解析
答案:

前者指针指向的内容不可改(内容只读);后者指针本身的指向不可改(指针只读)。

题 8 解析
答案:
int **pp;
详解:

二级指针,存放的是一级指针的地址。

题 9 解析
答案:
对。
详解:

void* 是通用指针,C 语言允许它隐式转换为任意类型指针(注意:C++ 不允许)。

题 10 解析
答案:
野指针解引用。
详解:

p 没有初始化,指向随机地址。写入可能导致程序立即崩溃。

题 11 解析
答案:
相加无意义;相减有意义。
详解:

两个地址相加没有物理意义。同类型指针相减表示中间相隔的元素个数(不是字节数)。

题 12 解析
答案:
0x12
详解:

大端序:高位字节存低地址。p 指向首字节(低地址),所以拿到的是高位数据 0x12

题 13 解析
答案:

野指针:未初始化的指针。
悬空指针:指向已释放内存的指针。

题 14 解析
答案:
只读承诺。
详解:

告诉调用者:“把你的数据传给我,我保证只读取,绝不修改。”这是一种契约。

题 15 解析
答案:
会报警告。
详解:

a 的类型是 int (*)[4](指向包含4个int的数组的指针),p 的类型是 int *。虽然数值可能一样,但步长不同,类型不兼容。

日期:2025年2月12日
专栏:C语言

Read more

Ubuntu 系统下 Anaconda 完整安装与环境配置指南(附常见问题解决)

在数据分析、机器学习或深度学习领域,Anaconda 是必备工具之一 —— 它能一键管理 Python 环境和各类库,避免版本冲突问题。本文以 Ubuntu 系统为例,详细记录 Anaconda 从安装到验证的完整流程,同时针对 “conda 命令找不到” 等常见问题提供解决方案,新手也能轻松上手。 一、安装前准备 1. 确认安装包来源 若已有本地安装包(如本文中存放于 data1/soft 目录的 Anaconda3-2021.05-Linux-x86_64.sh),可直接使用;若无,需从 Anaconda 官网 下载对应 Linux 版本的 .sh 格式安装包(建议选择 Python 3.x 系列,兼容性更强)。 2. 打开终端

By Ne0inhk

K8S(十七)—— Kubernetes集群可视化工具Kuboard部署与实践指南

文章目录 * 前言 * 一、Kuboard部署准备 * 1.1 前提条件 * 1.2 Kuboard官网参考 * 二、Kuboard部署步骤(Master节点执行) * 2.1 执行部署命令 * 2.2 检查Kuboard相关Pod状态 * 2.3 检查Kuboard服务暴露情况 * 三、Kuboard界面访问与集群导入 * 3.1 访问Kuboard Web界面 * 3.2 导入 Kubernetes 集群 * 四、通过YAML文件创建Nginx Pod * 4.1 编写Nginx Pod的YAML文件 * 4.2 执行YAML文件创建Pod * 4.3 验证Pod创建结果 * 五、在Kuboard界面查看容器组(Pod) * 六、

By Ne0inhk

5分钟彻底搞懂Docker Compose的restart参数(附真实生产案例)

第一章:Docker Compose重启机制概述 Docker Compose 提供了一套灵活的容器生命周期管理机制,其中重启策略(restart policy)是确保服务高可用性的核心组成部分。通过在 `docker-compose.yml` 文件中配置 `restart` 字段,用户可以定义容器在不同场景下的自动重启行为,从而提升应用的容错能力与稳定性。 重启策略类型 Docker Compose 支持以下几种常见的重启策略: * no:默认策略,容器退出时不自动重启。 * always:无论退出状态如何,始终重启容器。 * on-failure:仅在容器以非零退出码退出时重启,可选指定最大重试次数。 * unless-stopped:总是重启容器,除非容器被手动停止。 配置示例 以下是一个使用 `always` 重启策略的典型服务配置: version: '3.8' services: web: image: nginx:alpine restart: always

By Ne0inhk