当前位置: 首页 > news >正文

指针:C语言的灵魂之刃(一)

文章一览

  • 前言
  • 一、指针的提出
  • 二、指针类型
    • 2.1 类型本质:内存访问的`「解码器」`
    • 2.2 void指针:万用钥匙的两面性
  • 三、指针的「七宗罪」(常见错误)
    • 3.1 野指针
    • 3.2 内存泄漏
    • 3.3 越界访问
    • 3.4类型双标
  • 四、指针的运算
    • 4.1 指针加减整数:内存导航仪
    • 4.2 指针相减:元素距离测量
    • 4.3 关系运算:内存地址排序
  • 总结

前言

指针是C语言的核心机制,本质上是存储内存地址的变量。每个变量在内存中都有唯一的地址,通过&运算符可获取该地址,而指针则像“地址簿”一样记录并管理这些位置信息。例如,int *p = &a;表示p存储了整型变量a的内存地址。

指针的核心能力体现在两个方面:直接访问内存和高效操作数据。通过运算符解引用(如p = 10;),可直接读写目标内存;通过指针算术(如p++),可遍历数组等连续内存结构。这种特性使指针成为实现动态内存分配(malloc/free)、构建链表/树等数据结构、优化函数参数传递的关键工具。

学习指针需要理解“地址-值”的双重性,掌握指针与数组、函数、结构体的交互规则。虽然可能面临野指针、内存泄漏等风险,但合理使用指针能大幅提升程序效率和灵活性,是突破初级编程迈向系统级开发的重要里程碑。

一、指针的提出

指针是一个重要的计算机科学概念,主要在编程语言中使用。它可以被视为一个变量,其值是指向另一个变量或函数内存地址的引用。
指针在C语言口中扮演着核心角色,它允许程序间接访问和操作内存中的数据。

int var = 42;      // 在内存0x1000位置存储42
int *ptr = &var;   // ptr的值是0x1000

指针声明时的符号是*, 但是这个*在不同的场景下,所表达的含义是不同的,具体如下:

星号(*)的三种身份:
声明时:int * 表示"指向整型的指针类型"
解引用时:*ptr 表示"取出该地址存储的值"
运算时:* 可作为乘号(但编译器能区分语境)

总结来说,指针就是一个变量,用来存放地址的变量

二、指针类型

指针的类型决定了指针可以指向的数据类型和步长(每次递增或递减的字节数)。例如,int类型的指针用于指向整型数据,每次递增或递减都会影响到相邻的整型数据。

2.1 类型本质:内存访问的「解码器」

int n = 0x12345678;
char *pc = (char*)&n;  // 按字节解析
int *pi = &n;          // 按4字节整型解析

核心作用:

  • 访问宽度:sizeof(*pointer)决定每次访问的字节数
  • 解码方式:指导编译器如何解释内存中的二进制数据
  • 指针运算:p+1的实际地址增量 = sizeof(指针类型)

2.2 void指针:万用钥匙的两面性

有一类特殊类型的指针(void*)

void *vp = malloc(100);  // 通用存储容器

这个类型是一个无类型指针,他不表示任何类型。
作为一个无类型指针它的优势是:

  • 可以作为泛型函数参数:如qsort(void *base, ...)
  • 用于实现内存操作函数:如memcpy(void* dest, ...)

但是在使用过程中一定要注意,作为无类型指针,无法直接参与运算(编译器不知道你要走多少),也不可以直接解引用(编译器不知道你要把多少内存拿出来)。
要想对无类型指针进行操作,需要进行强制类型转换:

float f = 3.14f;
void *vp = &f;
int *ip = (int*)vp;  // 强制重新解释二进制位

结果:

IEEE754浮点:0x4048f5c3
转换为int:1085488611
  • 硬件视角:CPU不关心类型,只按指令处理比特位

这时候发现不同类型进行指针转换会出现读取错误,这个错误与不同的数据在内存中的存储方式有关。

所以说在类型转换的时候要遵循一定的准则:
安全转换四原则

  1. 大小匹配检查:
assert(sizeof(Source) == sizeof(Dest));
  1. 对齐要求验证:
_Static_assert(_Alignof(int) <= _Alignof(float), 
             "Alignment mismatch");
  1. 类型双关的正确方式:
int64_t num = 0x12345678;
double d;
memcpy(&d, &num, sizeof(d));  // 标准允许的复制方式
  1. 限定符传播规则:
const void *cvp = &x;
int *ip = (int*)cvp;  // 丢失const限定

三、指针的「七宗罪」(常见错误)

指针作为一个底层操作,它使得我们可以操作计算机内存,但是随之而来的便是一系列问题,对于内存的操作必须慎之又慎,否则将会引起严重的错误,接下来介绍指针的几种常见错误:

3.1 野指针

 int *p; *p = 5; (未初始化的指针)

未初始化的指针或非法指针使用可能导致程序崩溃。在使用指针前,必须确保它指向一个合法的内存地址。
野指针的成因通常有以下几种:

  • 指针未初始化
  • 指针越界访问
  • 指针指向的空间释放 (尽量不要返回临时变量的地址)

关于如何避免野指针,要注意几个方面:

  • 指针初始化 不知道初始化指向谁时,可以赋一个空指针 (NULL)
  • 小心指针越界
  • 指针指向空间释放即置为空
  • 使用指针之前检测指针的有效性

3.2 内存泄漏

malloc()后忘记free()

malloc是向堆空间申请一段内存,向堆申请的内存在没有手动释放时,不会自动释放,所以不及时释放可能会导致 栈溢出错误,在实际开发中可能会造成严重的系统奔溃。

3.3 越界访问

int arr[3]; *(arr+5) = 10;

由于指针操作,CPU不关心类型,只是按照bit位进行操作,这意味这系统不会进行类型安全检查,所以越界错误常有发生,实际编程中一定要注意这个错误。

3.4类型双标

float *p = (float*)&var;int变量强行按float解释)

除非确实有需要,否则将不同的类型指针进行转换极易操作数据读取的错误,得不到需要的结果。

四、指针的运算

4.1 指针加减整数:内存导航仪

int arr[5] = {10,20,30,40,50};
int *p = &arr[1];  // 指向20

// 运算演示
p += 2;    // 现在指向40
p -= 3;    // 现在指向10(首元素)

指针的加减运算是与指针类型有关的,指针的类型决定了指针的步进距离:

char *cp = (char*)arr;
cp += 5;  // 移动5字节(可能跨越多个int元素)

double *dp = (double*)arr;
dp += 1;  // 移动8字节(假设sizeof(double)=8)

同样的使用指针操作时要注意越界问题

4.2 指针相减:元素距离测量

int *start = &arr[0];
int *end = &arr[4];
printf("%d", start-end);
  • 得到的是指针之间得元素个数,小地址减大地址会得到负数。指针要指向同一块空间,否则会出现错误。
  • 指针运算时会自动关注指针大小,以类型大小为单位

4.3 关系运算:内存地址排序

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个位置的指针比较
也就是说:
关键限制

  • 允许比较范围:arr[0] 到 arr[N](含结尾后的位置)
  • 禁止比较范围:arr[-1] 之前的位置
    合法操作
int arr[5];
int *p = arr;

// 合法比较(结尾后位置)
while (p < arr+5) {  // 等价于 p <= arr+4
    *p++ = 0;
}

// 合法指针运算
int *end = arr + 5;  // 指向结尾后位置
for (p=arr; p != end; p++) { ... }

非法操作

int arr[5];
int *p = arr - 1;  // 未定义行为

// 错误比较(头前位置)
if (p > arr) { ... }  // 违反标准规定

// 错误运算导致的崩溃
*p = 10;  // 可能触发段错误

总结

以上就是指针的开篇,讲了指针的定义、指针的常见错误、指针的类型以及指针的运算,后面会持续讲解指针与数组、多级指针以及指针的各种混合类型。

相关文章:

  • 全面适配iOS 18.4!通付盾加固产品全面升级,护航App安全上架
  • node-red
  • NLP 面试细碎知识点 ① Transformer模型Q、K、V参数的作用
  • CI/CD(六) helm部署ingress-nginx(阿里云)
  • Netty和Project Reactor如何共同处理大数据流?
  • pytorch构建线性回归模型
  • 动捕技术革新虚拟直播:解码虚拟主播的“拟真感“破局之路
  • WEB安全--SQL注入--SQL注入的危害
  • 补Java基础之重生(13)类与对象(补充版)+面向对象综合案例
  • GPIO八种模式的应用场景总结
  • 动态规划~01背包问题
  • System.arraycopy 在音视频处理中的应用
  • 深入剖析 Android Compose 框架的自动动画:AnimatedVisibility 与 AnimatedContent(二十四)
  • std::endl为什么C++ 智能提示是函数?
  • 内核中的互斥量
  • 产品经理六题汇总
  • 图解AUTOSAR_CP_LargeDataCOM
  • PPT 转高精度图片 API 接口
  • 低代码平台中的原子组件
  • 再读强化学习24March
  • 云南一男子持刀致邻居3死1重伤案二审开庭,未当庭宣判
  • 《新时代的中国国家安全》白皮书(全文)
  • 成都锦江区一在建工地起火,致2人遇难1人受伤
  • 竞彩湃|热刺、曼联一周双赛不易,勒沃库森能否欢送阿隆索
  • 中美会谈前都发生了什么?美方为何坐不住了?
  • 体坛联播|穆勒主场完成拜仁谢幕战,山西车队再登环塔拉力赛