嵌入式C语言:什么是指针?

目录

一、指针的基本概念

1.1. 定义指针

1.2. 赋值给指针

1.3. 解引用指针

1.4. 指针运算

1.5. 空指针

1.6. 函数参数

1.7. 数组和指针

1.8. 示例代码

二、指针在内存中的表示

2.1. 内存地址存储

2.2. 内存模型

2.3. 指针与硬件交互

2.4. 示例代码

三 、指针的重要性

3.1. 访问硬件寄存器

3.2. 中断服务程序(ISR)中的指针

3.3. 动态内存分配

3.4. 函数指针

3.5. 指针数组和数组指针

3.6. 指针与结构体

3.7. 优化性能

3.8. 安全性考虑


在嵌入式系统编程中,C语言指针的使用非常普遍且重要。指针允许直接访问和操作内存地址,在嵌入式系统中尤其关键,可以高效地管理有限的硬件资源。

一、指针的基本概念

在C语言中,指针是一种特殊的变量类型,它存储的是另一个变量的内存地址,而不是数据值本身。就像是一个指向内存中某个特定位置的 “路标”,这个位置可以是一个变量、一个数组元素或者是一个函数的入口地址。【C语言进阶】指针详解-CSDN博客

1.1. 定义指针

指针变量通过在其类型前加上星号(*)来定义。意味着该变量将存储一个内存地址,而不是数据值。

int *ptr; // ptr 是一个指向 int 类型数据的指针

1.2. 赋值给指针

指针变量可以被赋予一个变量的地址。通常使用地址运算符(&来获取变量的地址。

int a = 5;
int *ptr = &a; // ptr 现在存储了变量 a 的内存地址

1.3. 解引用指针

使用星号(*)作为解引用运算符来获取指针所指向的值。

int value = *ptr; // value 现在存储了 ptr 所指向的 int 类型的值,即变量 a 的值 5

1.4. 指针运算

指针可以进行加减运算,但这些运算不是基于字节,而是基于指针所指向的数据类型的大小

int arr[10]; // 定义一个包含 10 个 int 类型元素的数组
int *p = arr; // p 指向数组的第一个元素
p++; // p 现在指向数组的第二个元素(因为 p 是指向 int 的指针,所以 +1 实际上跳过了 sizeof(int) 个字节)

1.5. 空指针

空指针(NULL)是一个特殊的指针值,表示它不指向任何有效的内存地址。将指针初始化为 NULL 是一种常见的做法,以避免野指针问题。

int *p = NULL; // p 是一个空指针,不指向任何有效的内存地址

1.6. 函数参数

指针经常用作函数参数,以允许函数修改调用者的变量

#include <stdio.h>void increment(int *x)
{(*x)++; // 修改指针所指向的变量的值
}int main() {int num = 5;increment(&num); // 传递 num 的地址给函数// num 现在等于 6printf("num = %d", num);return 0;
}

1.7. 数组和指针

数组名本身就是一个指向数组第一个元素的指针(常量指针),但数组名和指针之间也有一些重要的区别和注意事项。

#include <stdio.h>int main() {int arr[5] = {1, 2, 3, 4, 5};int *p = arr; // p 指向数组的第一个元素// 使用指针遍历数组for (int i = 0; i < 5; i++) {printf("%d ", *(p + i)); // 输出数组的每个元素}
}

1.8. 示例代码

以下是一个完整的示例代码,演示了上述所有指针的基本概念:

#include <stdio.h>void increment(int *x) {(*x)++;
}int main() {int a = 5;int *ptr = &a; // 定义指针并赋值printf("a 的值是: %d\n", a);printf("ptr 指向的值是: %d\n", *ptr);increment(ptr); // 通过指针修改 a 的值printf("调用 increment 后,a 的值是: %d\n", a);int arr[5] = {1, 2, 3, 4, 5};int *p = arr;printf("数组元素是:\n");for (int i = 0; i < 5; i++) {printf("%d ", *(p + i));}printf("\n");int *nullPtr = NULL; // 定义空指针if (nullPtr == NULL) {printf("nullPtr 是一个空指针\n");}return 0;
}

运行此代码将输出:

a 的值是: 5
ptr 指向的值是: 5
调用 increment 后,a 的值是: 6
数组元素是:
1 2 3 4 5 
nullPtr 是一个空指针

二、指针在内存中的表示

2.1. 内存地址存储

指针变量本身占用一定的内存空间(通常是几个字节,取决于平台和编译器),用于存储另一个变量的内存地址。在32位系统中,指针通常占用4个字节;在64位系统中,指针通常占用8个字节

2.2. 内存模型

在嵌入式系统中,内存通常分为几个不同的区域:代码区(存储程序代码)、数据区(包括全局变量和静态变量)、堆区(动态分配的内存)、栈区(用于函数调用和局部变量)。指针可以指向这些区域中的任何一个。嵌入式C语言:内存管理-CSDN博客

2.3. 指针与硬件交互

在嵌入式系统中,指针经常用于与硬件寄存器交互。硬件寄存器的地址是固定的,因此指针可以用来直接访问这些寄存器,从而控制硬件的行为。

2.4. 示例代码

以下是一个简单的嵌入式C语言示例,展示了指针的使用:

#include <stdint.h>
#include <stdio.h>// 假设有一个硬件寄存器的地址是 0x40021000
#define HARDWARE_REGISTER ((volatile uint32_t *)0x40021000)int main() {int a = 10;int *ptr = &a; // 定义一个指向 int 的指针,并指向变量 a// 使用指针访问和修改变量 a 的值printf("a 的原始值是: %d\n", a);*ptr = 20; // 通过指针修改 a 的值printf("通过指针修改后,a 的值是: %d\n", a);// 假设我们要设置硬件寄存器的值*HARDWARE_REGISTER = 0xDEADBEEF; // 直接访问硬件寄存器并设置其值// 注意:在实际嵌入式系统中,对硬件寄存器的访问可能需要特定的同步或保护机制,// 这里为了简化示例而省略了这些细节。return 0;
}

这里的volatile关键字很重要,它告诉编译器这个变量(指针所指向的寄存器)可能会被硬件或其他异步因素改变,编译器不应该对涉及这个变量的操作进行优化。

代码中的 HARDWARE_REGISTER 宏定义仅用于说明目的,并不代表一个真实的硬件寄存器地址。在实际嵌入式系统中,需要查阅硬件手册来获取正确的寄存器地址。此外,对硬件寄存器的访问通常涉及到特定的内存映射和访问权限,这些都需要根据具体的硬件平台来处理。

在嵌入式系统中使用指针时,需要特别注意内存对齐、内存访问权限(如读/写权限)、以及硬件特定的限制。不正确的指针操作可能会导致程序崩溃、硬件损坏或不可预测的行为。

三 、指针的重要性

在嵌入式C语言编程中,指针的使用至关重要,它们不仅用于基本的内存访问和操作,还广泛用于与硬件交互、处理中断、管理数据结构以及优化性能。

3.1. 访问硬件寄存器

在嵌入式系统中,指针经常用于直接访问硬件寄存器的地址。由于硬件寄存器的地址是固定的,因此可以使用指向特定地址的指针来读写这些寄存器。允许程序员直接控制硬件的行为,如设置时钟频率、配置外设接口等。

3.2. 中断服务程序(ISR)中的指针

在嵌入式系统中,中断服务程序经常需要使用指针来访问全局变量或共享数据结构。这些指针通常在中断的上下文中注册,并在中断发生时被用来访问或修改数据。

volatile uint8_t *ledStatusPtr;  // 全局变量指针,指向LED状态数组void ISR_ButtonPress(void) {// 假设ledStatusPtr已经在某处被正确初始化*ledStatusPtr ^= 0x01;  // 切换LED状态(假设是单个LED)
}void initializeLEDStatusPointer(uint8_t *ledArray) {ledStatusPtr = ledArray;  // 在中断服务程序外部初始化指针
}

3.3. 动态内存分配

尽管在资源受限的嵌入式系统中动态内存分配(如使用mallocfree)需要谨慎使用,但在某些情况下,它仍然是必要的。例如,当处理动态大小的数据结构(如链表、队列等)时,指针可以用于动态分配和释放内存。

#include <stdlib.h>typedef struct {int data;struct Node *next;
} Node;Node* createNode(int data) {Node *newNode = (Node *)malloc(sizeof(Node));  // 动态分配内存if (newNode != NULL) {newNode->data = data;newNode->next = NULL;}return newNode;
}void freeList(Node *head) {Node *temp;while (head != NULL) {temp = head;head = head->next;free(temp);  // 释放内存}
}

3.4. 函数指针

函数指针在嵌入式系统中非常有用,特别是在实现回调机制、状态机或事件驱动架构时。它们允许程序员在运行时决定调用哪个函数,从而提供更大的灵活性和可配置性。

typedef void (*CallbackFunction)(int);void onDataReceived(int data) {// 处理接收到的数据...
}void registerCallback(CallbackFunction callback) {// 假设在某个地方保存了这个回调,以便稍后调用// 例如,在中断服务程序中callback(42);  // 调用注册的回调
}int main() {registerCallback(onDataReceived);  // 注册回调// 其他代码...return 0;
}

3.5. 指针数组和数组指针

在嵌入式系统中,处理大量数据时,指针数组和数组指针非常有用。指针数组允许存储多个变量的地址,而数组指针则指向一个数组的首地址。这些结构在处理多维数组、字符串数组或复杂数据结构时特别有用。【C语言】语义陷阱探秘(一):指针与数组-CSDN博客

#include <stdio.h>int main() {int arr[3] = {1, 2, 3};int *arrPtr[3] = {&arr[0], &arr[1], &arr[2]};  // 指针数组,存储数组元素的地址// 使用指针数组访问数组元素for (int i = 0; i < 3; i++) {printf("arrPtr[%d] = %d\n", i, *arrPtr[i]);}int (*ptrToArray)[3] = &arr;  // 数组指针,指向整个数组printf("ptrToArray[1] = %d\n", (*ptrToArray)[1]);  // 使用数组指针访问数组元素return 0;
}

3.6. 指针与结构体

在嵌入式系统中,结构体通常用于表示复杂的数据结构,如硬件配置、通信协议包等。指针常用于访问和修改结构体成员。 

typedef struct {uint8_t address;uint8_t data;
} I2CPacket;void sendI2CPacket(I2CPacket *packet) {// 假设有一个发送I2C包的函数// 使用packet指针访问和发送地址和数据
}

3.7. 优化性能

在嵌入式系统中,性能通常是一个关键因素。通过使用指针,可以直接访问和修改内存中的数据,从而避免不必要的函数调用或数据复制。此外,指针还可以用于实现高效的算法和数据结构,如快速排序、哈希表等。

3.8. 安全性考虑

虽然下面的代码示例没有直接展示安全性考虑(如访问未初始化的指针、越界访问数组等),但强调了在使用指针时应该遵循的最佳实践。

// 初始化指针的最佳实践
int *safePtr = NULL;
int data = 10;
safePtr = &data;  // 指向有效数据// 检查空指针
if (safePtr != NULL) {// 安全地使用指针...
}// 避免越界访问数组
int myArray[5];
for (int i = 0; i < 5; i++) {myArray[i] = i * 2;  // 安全地访问数组元素
}
// 注意:不要访问 myArray[5] 或更高的索引,这是越界的。

总之,在嵌入式C语言编程中,指针是强大且灵活的工具。它们允许程序员直接访问和控制硬件、高效地管理内存和数据结构、实现回调和事件驱动机制等。然而,使用指针时也需要格外小心,以确保代码的安全性和可靠性。

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

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

相关文章

带格式 pdf 翻译

支持 openAI 接口&#xff0c;国内 deepseek 接口兼容 openAI 接口&#xff0c; deepseek api 又非常便宜 https://pdf2zh.com/ https://github.com/Byaidu/PDFMathTranslate

【redis初阶】初识Redis

目录 一、初识Redis 二、盛赞 Redis 三、Redis 特性 3.1 速度快 ​编辑3.2 基于键值对的数据结构服务器 3.3 丰富的功能 3.4 简单稳定 &#x1f436; 3.6 持久化&#xff08;Persistence&#xff09; 3.7 主从复制&#xff08;Replication&#xff09; 3.8 高可用&#xff08;H…

虚拟机Linux Red Hat 7.9 Docker部署.Net 7 Zr.Admin项目(后端)

0、环境信息 应用部署在虚拟机里的docker&#xff0c;里面的应用访问宿主主机的MySQL 1、开启MySQL远程访问 使用非安装版MySQL参考Windows 使用 非安装版MySQL 8 为了避免出现 Host is not allowed to connect to this MySQL server 使用root用户登录 cmd进入到MySQL的bi…

UE小白学习日记

Level UE中的Level(关卡)和Unity中的Scene(场景)在概念和用途上非常相似,都是用来组织和管理3D环境的基本单位。让我为您详细对比一下: 相似之处: 它们都是游戏世界的容器,可以包含游戏对象、光照、地形等元素都支持场景/关卡的切换和加载都可以用来划分游戏内容,比如不同关…

cmake - build MS STL project

文章目录 cmake - build MS STL project概述笔记END cmake - build MS STL project 概述 MS在github上开源了VS IDE 用的STL实现。 想看看微软的测试用例中怎么用STL. 想先用CMake编译一个MS STL发布版出来。 笔记 CMake需要3.30以上, 拟采用 cmake-3.30.6-windows-x86_64.…

微信小程序之历史上的今天

微信小程序之历史上的今天 需求描述 今天我们再来做一个小程序&#xff0c;主要是搜索历史上的今天发生了哪些大事&#xff0c;结果如下 当天的历史事件或者根据事件选择的历史事件的列表&#xff1a; 点击某个详细的历史事件以后看到详细信息&#xff1a; API申请和小程序…

错误修改系列---基于RNN模型的心脏病预测(pytorch实现)

前言 前几天发布了pytorch实现&#xff0c;TensorFlow实现为&#xff1a;基于RNN模型的心脏病预测(tensorflow实现)&#xff0c;但是一处繁琐地方 一处错误&#xff0c;这篇文章进行修改&#xff0c;修改效果还是好了不少&#xff1b;源文章为&#xff1a;基于RNN模型的心脏病…

vue.js+vite搭建一个简单的新春祈福活动网站

vue.jsvite搭建一个简单的新春祈福活动网站&#xff01;使用canvas技术&#xff0c;绘制视觉特效。 功能有&#xff1a;燃放烟花&#xff0c;和撞钟祈福。祈福撞钟我设计了是按钮事件&#xff0c;播放一个mp4动画&#xff0c;配上播放一段撞钟的生效文件mp3. <template>&…

有机物谱图信息的速查技巧有哪些?

谱图信息是化学家解读分子世界的“语言”&#xff0c;它们在化学研究的各个领域都发挥着不可或缺的作用。它们是理解和确定分子结构的关键&#xff0c;对化学家来说极为重要&#xff0c;每一种谱学技术都提供了不同的视角来观察分子&#xff0c;从而揭示其独特的化学和物理特性…

视频转码对画质有影响吗?视频融合平台EasyCVR支持哪些转码格式?

视频转码过程是将视频文件从一种编码格式转换为另一种格式的过程&#xff0c;这一过程在现代数字媒体中扮演着至关重要的角色。众所周知&#xff0c;视频转码不仅仅是简单的格式转换&#xff0c;它涉及多个关键参数的改变&#xff0c;例如视频编码格式、比特率、分辨率以及帧率…

微信小程序防止重复点击事件

直接写在app.wpy里面&#xff0c;全局可以调用 // 防止重复点击事件preventActive(fn) {const self this;if (this.globalData.PageActive) {this.globalData.PageActive false;if (fn) fn();setTimeout(() > {self.globalData.PageActive true;}, 3000); //设置该时间内…

STM32-WWDG/IWDG看门狗

WWDG/IWDG一旦开启不能关闭&#xff0c;可通过选项字节在上电时启动硬件看门狗&#xff0c;看门狗计数只能写入不能读取。看门狗启用时&#xff0c;T6bit必须置1&#xff0c;防止立即重置。 一、原理 独立看门狗-超时复位 窗口看门狗-喂狗&#xff08;重置计数器&#xff0c;…

C++初阶—CC++内存管理

第一章&#xff1a;C/C内存分布 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] { 1, 2, 3, 4 };char char2[] "abcd";const char* pChar3 "abcd";int* ptr1 (int*)malloc(si…

排序的本质、数据类型及算法选择

排序的本质、数据类型及算法选择 一、排序的本质二、排序的数据类型三、排序算法的选择依据 前两天老金写了篇 “十大排序简介”&#xff0c;有点意犹未尽&#xff0c;这一回老金想把排序连根拔起&#xff0c;从排序的本质说道说道。 一、排序的本质 从字面上理解&#xff0c…

arcgisPro加载天地图(CGCS2000)影像

1、注册天地图账号&#xff1b; 2、申请key&#xff1b; 3、添加WMTS服务器。 这里已经办好了前两步&#xff0c;下面详细介绍最后一步。 添加WMTS服务器。 在天地图网站&#xff0c;找到如下页面&#xff0c; 复制网址&#xff0c;如&#xff1a;http://t0.tianditu.gov.cn…

【测试】持续集成CI/CD

近期更新完毕&#xff0c;建议关注收藏点赞&#xff5e; 目录 概括gitJenkinspostman集成jenkins代码集成jenkins 概括 CI/CD stands for Continuous Integration and Continuous Deployment 定义 团队成果持续集成到公共平台。一天可以集成1次or多次 本地代码管理 git 远程代…

python基础和redis

1. Map函数 2. filter函数 numbers generate_numbers() filtered_numbers filter(lambda x: x % 2 0, numbers) for _ in range(5):print(next(filtered_numbers)) # 输出: 0 2 4 6 83. filter map 和 reduce 4. picking and unpicking 5. python 没有函数的重载&#xff0…

【再谈设计模式】模板方法模式 - 算法骨架的构建者

一、引言 在软件工程、软件开发过程中&#xff0c;我们经常会遇到一些算法或者业务逻辑具有固定的流程步骤&#xff0c;但其中个别步骤的实现可能会因具体情况而有所不同的情况。模板方法设计模式&#xff08;Template Method Design Pattern&#xff09;就为解决这类问题提供了…

安卓app抓包总结(精)

前言 这里简单记录一下相关抓包工具证书的安装 burp证书安装 安装证书到移动设备(安卓7以后必须上传到设备系统根证书上) 导出证书 openssl x509 -inform DER -in cacert.der -out cacert.pem 转换格式 openssl x509 -inform PEM -subject_hash_old -in cacert.pem …

【pycharm发现找不到python打包工具,且无法下载】

发现找不到python打包工具,且无法下载 解决方法&#xff1a; 第一步&#xff1a;安装distutils&#xff0c;在CMD命令行输入&#xff1a; python -m ensurepip --default-pip第二步&#xff1a;检查和安装setuptools和wheel&#xff1a; python -m pip install --upgrade …