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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络相关习题整理

第一讲 传输媒介 【知识点回顾】 两种导线可以减小电磁干扰&#xff1a; 双绞线&#xff08;分为非屏蔽双绞线、屏蔽双绞线&#xff09;&#xff08;RJ-45用&#xff09;同轴电缆&#xff08;短距离使用&#xff09;网络通信的基本单位&#xff1a;位&#xff08;bit&#xff…

应急响应之入侵排查(下)

一.进程排查 1.Windows 任务管理器查看 在 Windows 系统中&#xff0c;可通过任务管理器查看进程信息。操作步骤为&#xff1a;在任务管理器界面&#xff0c;于 “查看” 选项中选择 “选择列”&#xff0c;随后添加 “映像路径名称” 和 “命令行”&#xff0c;以此查看更多进…

极狐GitLab 正式发布安全版本17.7.1、17.6.3、17.5.5

本分分享极狐GitLab 补丁版本 17.7.1, 17.6.3, 17.5.5 的详细内容。这几个版本包含重要的缺陷和安全修复代码&#xff0c;我们强烈建议所有私有化部署用户应该立即升级到上述的某一个版本。对于极狐GitLab SaaS&#xff0c;技术团队已经进行了升级&#xff0c;无需用户采取任何…

力扣经典二分题:4. 寻找两个正序数组的中位数

题目链接&#xff1a;4. 寻找两个正序数组的中位数 - 力扣&#xff08;LeetCode&#xff09; 一、题目分析 这道题目是让我们在 两个正序的数组中寻找中位数已知两个数组的大小分别是&#xff1a;int m nums1.size(),n nums2.size();中位数性质1&#xff1a;中位数左侧元素 …

安装yarn时显示npm使用淘宝镜像安装报错

问题描述&#xff1a; npm使用淘宝镜像安装报错 错误原因&#xff1a; 淘宝原镜像域名&#xff08;registry.npm.taobao.org&#xff09;的 HTTPS 证书正式到期&#xff0c;npm 淘宝镜像已经从 registry.npm.taobao.org 切换到了 registry.npmmirror.com。解决方案&#xff1a;…

【Python】Python之Selenium基础教程+实战demo:提升你的测试+测试数据构造的效率!

这里写目录标题 什么是Selenium&#xff1f;Selenium基础用法详解环境搭建编写第一个Selenium脚本解析脚本脚本执行结果常用的元素定位方法常用的WebDriver方法等待机制 Selenium高级技巧详解页面元素操作处理弹窗和警告框截图和日志记录多窗口和多标签页操作 一个实战的小demo…

Seata搭建

1.初识Seata Quick Start | Apache Seata 官网 2.准备nacos和 seata 启动nacos startup.cmd -m standalone账号nacos 密码nacos 搭建seata TC 这里下载的 1.4.2 seata-server-1.4.2 1.修改seata配置文件 registry.conf 这里我们使用nacos作为注册中心 和 配置中心 r…

selenium+pyqt5自动化工具总结

说明&#xff1a;本工具是&#xff0c;操作外部google浏览器、selenium是无法操作qt界面中嵌套的浏览器的&#xff0c; 工具在后面 1. 代码结构 pycharm打开的文件下&#xff0c;再写一个子文件&#xff0c;文件导入的时候把子文件名带上 这样就可以在 外层使用命令 pyinst…

.NET 终止或结束进程

如何使用 C# 终止进程。 使用简单的方法终止.NET中的现有进程Process.Kill()。有一个可选参数 true 或 false&#xff0c;用于结束与要结束的进程相关的所有子进程。 了解如何创建流程。 结束当前进程&#xff1a; System.Diagnostics.Process.GetCurrentProcess().Kill(tru…

怎么实现Redis的高可用?

大家好&#xff0c;我是锋哥。今天分享关于【怎么实现Redis的高可用&#xff1f;】面试题。希望对大家有帮助&#xff1b; 怎么实现Redis的高可用&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 为了实现 Redis 的高可用性&#xff0c;我们需要保证在发…

大数据技术实训:Hadoop完全分布式运行模式配置

准备&#xff1a; 1&#xff09;准备3台客户机&#xff08;关闭防火墙、静态ip、主机名称&#xff09; 2&#xff09;安装JDK 3&#xff09;配置环境变量 4&#xff09;安装Hadoop 5&#xff09;配置环境变量 6&#xff09;配置集群 7&#xff09;单点启动 8&#xff09;配置ss…

【Uniapp-Vue3】Prop校验与prop默认值用法及循环遍历数组对象

一、prop校验 如果我们在想要限制prop的类型&#xff0c;就可以在接收prop的时候对接收类型进行限制&#xff1a; defineProps({ 属性名:{ type:类型 } }) 需要注意类型的首字母大写 但是设置了传入参数类型限制并不能严格限制&#xff0c;只会在后台进行提示&#xff1a; 二、…

开启Excel导航仪,跨表跳转不迷路-Excel易用宝

都2025年了&#xff0c;汽车都有导航了&#xff0c;你的表格还没有导航仪吗&#xff1f;那也太OUT了。 面对着一个工作簿中有N多个工作表&#xff0c;工作表中又有超级表&#xff0c;数据透视表&#xff0c;图表等元素&#xff0c;如何快速的切换跳转到需要查看的数据呢&#…

【Unity3D日常开发】Unity3D中适用WEBGL打开Window文件对话框打开/上传文件

推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享QQ群&#xff1a;398291828小红书小破站 大家好&#xff0c;我是佛系工程师☆恬静的小魔龙☆&#xff0c;不定时更新Unity开发技巧&#xff0c;觉得有用记得一键三连哦。 一、前言 Unity3D发布的WEBGL程序是不支持直接的I/O操…

《安富莱嵌入式周报》第348期:开源低功耗测试仪,开源创意万用表,续航100-300小时,开源PCB电机,自制shell和网络协议栈,开源水培自动化系统

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Tzr9Y3EQ7/ 《安富莱嵌入式周报》第348期&#xff1a;开源低功…

音频合成的常见问题

使用了1年多的音频合成&#xff0c;有些常见的问题分享给大家 。 一、音质问题 噪声 背景噪声&#xff1a;在音频合成过程中&#xff0c;可能会引入背景噪声。这可能是由于原始音频素材本身质量不佳&#xff0c;比如录制环境嘈杂&#xff0c;包含电脑风扇声、外界交通声等。当…

用AI技术提升Flutter开发效率:ScriptEcho的力量

引言 在当今快速发展的技术时代&#xff0c;Flutter作为一种跨平台开发框架&#xff0c;正在越来越多的开发者中崭露头角。它不仅能够为开发者提供一套代码同时部署到iOS和Android平台的解决方案&#xff0c;还能帮助企业节省人力成本和开发时间。然而&#xff0c;对于新手开发…

SmartAIChain荣获重要认可

2024年12月21日,洛杉矶尔湾市——在今年尔湾举办的交流会上,黄荣先生的SmartAIChain项目获得了重要认可。此次活动汇聚了来自各地的杰出人才以及社区代表,共同庆祝这一创新性艺术的时刻。 在活动中,核桃市议员伍立伦(Allen Wu)的代表黄冬平女士发言,并为黄荣先生颁发了荣誉证书…

EFT信号测试和电源测试经验笔记

1. 什么是EFT 标准&#xff1a;perlEC 61000-4-4 eft设备将群脉冲干扰加到信号或者电源上&#xff0c;常见的频率是 5K 100K 两个频率 电压 电源3k&#xff0c;信号2k -----电网设备 电源4K -------------------空调设备 大概就是下图这样的周期性脉冲 1.1 电源eft 通过信…

web前端学习总结(一)

web前端使用三项技术:html、css、javascript. 一、html:超文本标记语言&#xff0c;用于展示网页的框架。 <html> <head><title> </title></head><body><div> </div> <!--用于布局&#xff0c;占1行 --><span&g…