解码C语言指针

news/2025/9/19 10:45:51/文章来源:https://www.cnblogs.com/YouEmbedded/p/19100327

一、指针的定义与本质

1. 指针是什么?

指针是一种 存储变量内存地址 的特殊变量。所有数据存储在内存中,每个内存单元都有唯一地址(编号),指针通过记录地址实现对数据的间接访问。

2. 指针的核心作用

  • 直接操作内存:动态内存分配、硬件编程等。
  • 提高效率:传递大对象时避免复制(如结构体)。
  • 灵活数据结构:实现链表、树、图等动态结构。

二、指针基本语法

1. 声明指针

数据类型 *指针变量名;// *表示这是个指针变量

示例

int *p;// p指向int类型变量
char *str;// str指向char类型变量

2. 指针初始化

  • 取地址操作符 &:获取变量的内存地址。

    int num = 10;
    int *p = #// p指向num的地址
    
  • 直接赋值(需确保地址合法):

    int *p = (int *)0x00007FFF1234;// 避免直接操作未知地址
    

三、指针的解引用

  • 解引用操作符 * ****:通过指针访问或修改目标地址的数据。

    int num = 10;
    int *p = #printf("num的值 = %d\n", *p);// 输出10(访问值)
    *p = 20;// 修改num为20
    

四、指针的算术运算

指针的加减操作以 数据类型大小 为单位进行偏移。

1. 指针与整数运算

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;// p指向arr[0]p++;// p指向arr[1](地址+4字节,假设int为4字节)
printf("%d\n", *p);// 输出2p += 3;// p指向arr[4]
printf("%d\n", *p);// 输出5

2. 指针之间的减法

计算两个指针之间的元素距离(同类型指针):

int *p1 = &arr[0];
int *p2 = &arr[4];
printf("距离 = %ld\n", p2 - p1);// 输出4(间隔4个元素)

五、多级指针

  • 二级指针:指向指针的指针。
int num = 10;
int *p = #//数据类型 *指针变量名;
int **pp = &p;// pp指向p的地址
printf("%d\n", **pp);// 通过二级指针访问num的值 → 输出10

1. 遍历二维数组(数组指针)

#include <stdio.h>int main() {int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};int (*p)[3] = matrix;// 指向第一行的地址for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("%d ", p[i][j]);// 等价于 *(*(p + i) + j)}printf("\n");}return 0;
}

六、指针&数组&字符串

1. 数组名的本质

数组名是数组首元素的地址常量(不可修改)。

int arr[3] = {10, 20, 30};
int *p = arr;// p指向arr[0]
printf("%d\n", *(p + 1));// 右移一个int单位 → 输出20

2. 指针遍历数组

for (int *ptr = arr; ptr < arr + 3; ptr++) {printf("%d ", *ptr);// 输出10 20 30
}

3. 遍历字符串

char *p = "Hello";
while (*p != '\0') {printf("%c ", *p);// 输出 H e l l op++;
}

4. 字符串拼接(动态分配)

#include <stdlib.h>
#include <string.h>
char *concat(const char *s1, const char *s2) {char *result = malloc(strlen(s1) + strlen(s2) + 1);// +1 为 '\0'strcpy(result, s1);strcat(result, s2);return result;
}// 使用示例
char *str = concat("Hello, ", "world!");
printf("%s\n", str);// 输出 Hello, world!
free(str);// 必须释放内存

5. 字符串数组(指针数组)

char *fruits[] = {"Apple", "Banana", "Cherry"};// 指针数组
for (int i = 0; i < 3; i++) {printf("%s\n", fruits[i]);// 输出各水果名称
}
printf("%s\n",*(fruits + 1));//输出Banana,数组的第二个元素
printf("%c\n",*(*(fruits + 1) + 1));//输出a,Banana的第二个字符

七、指针与函数

1. 传递指针到函数

// 修改外部变量值
void increment(int *x) {(*x)++;
}int main() {int a = 5;increment(&a);// a变为6return 0;
}

2. 返回指针的函数(指针函数)

/* 返回数组最大元素的地址,只能返回静态变量的地址、动态开辟(malloc)的地址,全局变量
的地址等,不能返回局部变量的地址(否则程序会崩溃)
*/
int* find_max(int *arr, int size) {int *max = arr;for (int *p = arr; p < arr + size; p++) {if (*p > *max) max = p;}return max;
}
int* find_max(int *arr, int size) {static int max = *arr;for (int *p = arr; p < arr + size; p++) {if (*p > max) max = *p;}return &max; 
}

3. 函数指针

指向函数的指针,用于回调机制:

int add(int a, int b) { return a + b; }
int (*func_p)(int, int) = add;// 声明函数指针
printf("%d\n", func_p(3, 4));// 输出7

八、void * 指针

  • 通用指针,可指向任意类型数据。
  • 使用前需强制类型转换。
void *ptr;
int x = 10;
ptr = (void *)&x;// 合法
printf("%d\n", *(int *)ptr);// 需转换为int*

九、

核心规则:const 的位置决定保护的对象

const 在 * 的左边,保护的是指针所指向的数据*p 不可变)。

const 在 * 的右边,保护的是指针本身p 不可变)。


1. 指向常量的指针(Pointer to Constant)

格式: const 类型 *ptr 或 类型 const *ptr

含义: 指针 ptr 可以指向不同的地址,但不能通过 ptr 来修改它所指向的数据。

示例:

int a = 10, b = 20;// 指向常量的指针
const int *ptr1 = &a;// ptr1 指向一个const int
int const *ptr2 = &a;// 等价写法
// *ptr1 = 15;  // 错误!不能通过ptr1修改数据
a = 15;// 合法,a本身不是constptr1 = &b;// 合法,ptr1可以指向其他地址
// *ptr1 = 25;  // 错误!仍然不能通过ptr1修改数据

2. 指针常量(Constant Pointer)

格式: 类型 * const ptr

含义: 指针 ptr 自身是常量,一旦初始化就不能再指向其他地址,但可以通过 ptr 修改它所指向的数据。

示例:

int x = 10, y = 20;// 指针常量
int * const ptr = &x;// ptr是常量指针
*ptr = 15;// 合法,可以修改指向的数据
// ptr = &y;    // 错误!ptr本身是常量,不能改变指向

3. 指向常量的指针常量(Constant Pointer to Constant)

格式: const 类型 * const ptr

含义: 既不能修改指针 ptr 的指向,也不能通过 ptr 修改所指向的数据。

示例:

const int z = 30;
int w = 40;// 指向常量的指针常量
const int * const ptr = &z;// *ptr = 35;    // 错误!不能修改数据
// ptr = &w;     // 错误!不能修改指针指向
int read = *ptr;// 合法,只能读取

总结表格(带变量名)

声明格式 指针(ptr)是否可修改 数据(*ptr)是否可修改 说明
int *ptr 普通指针
const int *ptr 指向常量的指针
int * const ptr 指针常量
const int * const ptr 指向常量的指针常量

十、野指针

1. 野指针

野指针是指指向无效内存地址的指针。这些指针通常指向已经被释放或未分配的内存区域。对野指针进行操作会导致未定义行为


2.主要成因

成因一:指针未初始化

int *ptr;// 未初始化,值是随机的垃圾值
*ptr = 10;// 危险!向随机内存地址写入数据
printf("%d", *ptr);// 可能导致程序崩溃

成因二:malloc后未检查或释放后继续使用

int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {*ptr = 100;
}
free(ptr);// 释放内存
// ptr现在成为野指针*ptr = 200;// 危险!访问已释放的内存
printf("%d", *ptr);// 未定义行为

成因三:指向局部变量(函数返回后)

int* createWildPointer() 
{int local = 42;return &local;// 返回局部变量的地址
}int main() {int *ptr = createWildPointer();// ptr是野指针printf("%d", *ptr);// 危险!local已销毁return 0;
}

成因四:指针越界访问

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;for (int i = 0; i < 10; i++) 
{// 越界访问printf("%d ", *(ptr + i));// 后5次是野指针访问
}

成因五:指针运算错误

int *ptr = (int*)malloc(5 * sizeof(int));
// ... 使用ptr
free(ptr);int *ptr2 = ptr + 2;// ptr2也是野指针

3. 野指针的危害

  1. 程序崩溃:段错误(Segmentation Fault)
  2. 数据损坏:意外修改其他有效数据
  3. 安全漏洞:可能被利用进行攻击
  4. 难以调试:错误表现随机,难以追踪

4. 避免野指针的方法

方法一:总是初始化指针

int *ptr = NULL;// 初始化为NULL
int *ptr2 = (int*)malloc(sizeof(int));// 或初始化为有效地址
if (ptr2 != NULL) {*ptr2 = 10;
}

方法二:释放后立即置NULL

int *ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {*ptr = 100;printf("%d\n", *ptr);
}free(ptr);// 释放内存
ptr = NULL;// 立即置NULL
// 安全检查
if (ptr != NULL) 
{printf("%d\n", *ptr);// 不会执行} 
else 
{printf("指针已释放\n");
}

方法三:避免返回局部变量地址

// 错误做法
int* badFunction() {int local = 5;return &local;
}// 正确做法1:使用静态变量
int* goodFunction1() {static int staticVar = 5;// 静态变量生命周期延长return &staticVar;
}// 正确做法2:使用动态内存
int* goodFunction2() {int *dynamicVar = (int*)malloc(sizeof(int));if (dynamicVar != NULL) {*dynamicVar = 5;}return dynamicVar;
}// 正确做法3:通过参数返回
void goodFunction3(int **result) {*result = (int*)malloc(sizeof(int));if (*result != NULL) {**result = 5;}
}

方法四:使用函数返回值检查

int *createArray(int size) {if (size <= 0) {return NULL;// 返回NULL而不是野指针}return (int*)malloc(size * sizeof(int));
}int main() {int *arr = createArray(5);if (arr == NULL) {printf("内存分配失败\n");return 1;}// 使用arr...free(arr);arr = NULL;return 0;
}

方法五:封装内存管理函数

// 安全的malloc封装
void* safeMalloc(size_t size) {void *ptr = malloc(size);if (ptr == NULL) {fprintf(stderr, "内存分配失败\n");exit(EXIT_FAILURE);}return ptr;
}// 安全的free封装
void safeFree(void **ptr) {if (ptr != NULL && *ptr != NULL) {free(*ptr);*ptr = NULL;// 自动置NULL}
}int main() {int *arr = (int*)safeMalloc(5 * sizeof(int));// 使用arr...safeFree((void**)&arr);// 自动置NULL// 现在arr为NULL,不会成为野指针return 0;
}

5. 检测野指针的工具

使用Valgrind(Linux)

gcc -g program.c -o program
valgrind --leak-check=full ./program

十一、动态内存分配

使用指针管理堆内存(需引入 <stdlib.h>):

int *arr = (int *)malloc(5 * sizeof(int));// 分配5个int的空间
if (arr != NULL) {arr[0] = 100;
// 使用完毕后释放内存free(arr);arr = NULL;// 避免野指针
}

十二、指针的注意事项&应用场合

问题 说明 规避方法
野指针 指向未知内存的指针 初始化时置空,释放后置空
空指针解引用 对 NULL 指针进行操作导致崩溃 使用前检查指针是否为 NULL
内存泄漏 未释放动态分配的内存 确保每个 malloc 对应一个 free
指针类型不匹配 操作不同类型指针导致数据错误 避免强制类型转换的误用
  1. 当函数需要传递一个数组时,指针代替数组
  2. 当函数需要返回一个地址(例如函数需要返回数组)的时候,需要使用指针
    实现。
  3. 当函数需要改变实参的时候,需要使用指针实现。-----形参改变实参

十三、代码示例

1. 指针操作基本流程

#include <stdio.h>
int main() {int a = 10;int *p = &a;printf("a的地址 = %p\n", (void *)p);// 输出地址(如0x7ffd...)printf("a的值 = %d\n", *p);// 输出10*p = 20;printf("修改后a的值 = %d\n", a);// 输出20return 0;
}

2. 动态数组操作

#include <stdio.h>
#include <stdlib.h>
int main() {int size = 5;int *arr = (int *)malloc(size * sizeof(int));if (arr == NULL) {printf("内存分配失败!\n");return 1;}for (int i = 0; i < size; i++) {arr[i] = i * 10;}for (int *p = arr; p < arr + size; p++) {printf("%d ", *p);// 输出0 10 20 30 40}free(arr);arr = NULL;return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/907702.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

windows下Qt调用fftw库

环境:Windows 11 Qt:6.8.3 程序中需要用到fftw库来进行傅里叶变换,通过网上的资料,配置了很久一直没成功,后来发下还是没有配置正确,最后终于成功,顺便记录一下 1.下载fftw3.3.5 http://www.fftw.org/install/…

Gitee崛起:国产代码托管平台如何接棒CODING成为开发者新宠

Gitee崛起:国产代码托管平台如何接棒CODING成为开发者新宠 随着腾讯云宣布CODING DevOps系列产品逐步停止服务,中国开发者生态正在经历一次重要的基础设施迁移。在这场变革中,开源中国旗下Gitee平台凭借其十年本土化…

flask下的MySQL增查配置

flask下的MySQL增删配置 添加数据 @app.route(/add) def add_data():u = UserInfo()new_user1 = UserInfo(nickname=flask_test1, mobile=13888888888, signature=理想, create_time=datetime.now(), role_id=1)new_us…

AT_agc056_c [AGC056C] 01 Balanced

不难设 \(d_i\) 为前缀 \(i\) 中 \(0/1\) 数量的差值,显然有两个限制:\(d_{l - 1} = d_r\) \(|d_{i - 1} - d_i| \le 1\)可以差分约束,事实上,直接跑差分约束就可以得到字典序最小的构造了,这也算本题的价值之一。…

高效智能(到家服务)管理平台解决方案

一、项目背景 随着互联网技术的快速发展以及服务行业的数字化转型,传统的线下服务模式逐渐向线上迁移。为了提升服务效率、优化用户体验,并满足用户对快速响应和精准服务的需求,本项目旨在打造一个高效、智能的服务…

centos7卸载openjdk-java11

[root@localhost ~]# rpm -qa|grep -i openjdk java-11-openjdk-headless-11.0.23.0.9-2.el7_9.x86_64 java-11-openjdk-devel-11.0.23.0.9-2.el7_9.x86_64 java-11-openjdk-11.0.23.0.9-2.el7_9.x86_64# 卸载命令, yu…

jenkins的安装和配置

windows 安装 jenkins 自动化构建部署至linux服务器上 一、环境准备1、git安装环境 参考链接 https://www.cnblogs.com/yuarvin/p/12500038.html 2、maven安装环境,包括jdk环境安装 参考链接 https://www.cnblogs.com…

从MMoE到PLE:读懂多任务学习架构的渐进式演化

从MMoE到PLE:读懂多任务学习架构的渐进式演化从MMoE到PLE:读懂多任务学习架构的渐进式演化 引言 在多任务学习(MTL)领域,MMoE(Multi-gate Mixture-of-Experts)无疑是一个里程碑式的模型,它通过巧妙的软参数共享…

在VSCode中设置Qt编写环境

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

深入解析:【Day 52 】Linux-Jenkins

深入解析:【Day 52 】Linux-Jenkinspre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monac…

本土开发者如何选择代码管理工具?Gitee与GitHub深度对比解析

本土开发者如何选择代码管理工具?Gitee与GitHub深度对比解析 在数字化转型浪潮下,代码管理工具已成为开发者日常工作的必备基础设施。面对国内外众多选择,新手开发者往往陷入选择困难。本文将从本土化开发视角,深入…

MES系统核心组件

核心总览 在半导体工厂中,MES(Manufacturing Execution System,制造执行系统) 是最高层的指挥中枢,它负责管理生产线上从投料开始到成品产出的全部作业流程。而其他各种“XX Server”则是MES这个大脑指挥下的专业…

易基因:多组学整合分析揭示DNA甲基化与基因组改变在肿瘤进化中的协同驱动机制|Nat Genet/IF29重磅

大家好,这里是专注表观组学十余年,领跑多组学科研服务的易基因。 近日,伦敦大学学院癌症研究所Nnennaya Kanu和弗朗西斯克里克研究所Peter Van Loo团队合作在国际遗传学Top期刊《自然遗传学》(Nature Genetics)发…

AI 视频模型大比拼(2025年7月版):价格、效果与分辨率

AI 视频模型大比拼(2025年7月版):价格、效果与分辨率pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

为什么芯片行业需要私有化部署软件?

在芯片研发过程中,每天产生的大量设计数据、测试结果和知识产权,构成了企业的核心竞争优势。一旦这些数据泄露,不仅可能导致数百万美元的研发投入付诸东流,更可能让企业在全球竞争中失去领先地位。 近年来,芯片行…

C++ std::string

C++ 标准库中的 std::string 是处理字符串的核心类,封装了字符串的存储、管理和操作,相比 C 风格的 char* 更安全、易用。 1、基本概念 1.1 基本特性 std::string 定义在 <string> 头文件中(属于 std 命名空间…

MathType数学公式编辑器v7.9.1

MathType全球最受欢迎的专业数学公式编辑器工具软件,可视化公式编辑器轻松创建数学方程式和化学公式。兼容Office Word、PowerPoint、Pages、Keynote、Numbers 等700多种办公软件,用于编辑数学试卷、书籍、报刊、论文…

git常见冲突场景及解决办法 - 指南

git常见冲突场景及解决办法 - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…

有关字节的基础知识

什么是字节位(bit):是计算机 内部数据 存储的最小单位,11001100是一个八位二进制数。字节(byte):是计算机中 数据处理 的基本单位,习惯上用大写B来表示。字符:是指计算机中使用的字母、数字、字和符号 1bit表…

strip去符号前后对比

strip去符号前后对比 strip 是 binutils 中用于给二进制文件(可执行程序、静态库、动态库)去符号信息的工具。它只修改符号表,不碰指令内容,因此去除符号后的二进制文件中,各部分指令和原来是完全一一对应的,各s…