嵌入式C语言:结构体的多态性之结构体中的void*万能指针

目录

一、void*指针在结构体中的应用

二、实现方式

2.1. 定义通用结构体

2.2. 定义具体结构体

2.3. 初始化和使用

三、应用场景

3.1. 内存管理函数

3.2. 泛型数据结构(链表)

3.3. 回调函数和函数指针

3.4. 跨语言调用或API接口(模拟)

4.1. 类型安全问题

4.2. 内存管理问题

4.3. 代码可读性和可维护性问题

4.4. 并发访问问题

4.5. 性能问题


在嵌入式C语言编程中,结构体常常用于数据封装和组织。而在实现多态性(polymorphism)时,一个常见的技巧是使用void*类型的指针,即“万能指针”void*指针可以指向任何类型的数据,使得它成为实现泛型数据结构(如链表、栈、队列等)和函数(如通用排序、查找等)时非常有用的工具。

一、void*指针在结构体中的应用

在结构体中使用void*指针可以实现多种类型的数据存储。例如,可以创建一个通用的链表节点结构体,其中包含一个void*指针来指向任意类型的数据:

struct Node {void* data;struct Node* next;
};

data成员可以指向任何类型的数据,而next成员则指向链表中的下一个节点。

二、实现方式

2.1. 定义通用结构体

首先定义一个包含 void* 指针和类型标识的通用结构体,这个结构体可以作为基类来使用。

#include <stdio.h>// 定义一个基结构体
typedef struct {void* data;  // 万能指针,指向具体的数据int type;    // 类型标识,用于区分不同的数据类型
} GenericStruct;

2.2. 定义具体结构体

接着定义不同类型的具体结构体,这些结构体可以看作是派生类。 

// 定义具体结构体 1
typedef struct {int value;
} IntStruct;// 定义具体结构体 2
typedef struct {float value;
} FloatStruct;

2.3. 初始化和使用

在代码中初始化通用结构体,并根据不同的类型进行处理。 

// 初始化通用结构体
void initGenericStruct(GenericStruct* gs, void* data, int type) {gs->data = data;gs->type = type;
}// 处理通用结构体
void processGenericStruct(GenericStruct* gs) {switch (gs->type) {case 1: {IntStruct* intData = (IntStruct*)gs->data;printf("Integer value: %d\n", intData->value);break;}case 2: {FloatStruct* floatData = (FloatStruct*)gs->data;printf("Float value: %.2f\n", floatData->value);break;}default:printf("Unknown type\n");break;}
}int main() {IntStruct intObj = {42};FloatStruct floatObj = {3.14f};GenericStruct gs1, gs2;// 初始化通用结构体initGenericStruct(&gs1, &intObj, 1);initGenericStruct(&gs2, &floatObj, 2);// 处理通用结构体processGenericStruct(&gs1);processGenericStruct(&gs2);return 0;
}

 

三、应用场景

在嵌入式C语言编程中,结构体中的void*万能指针在实现多态性方面有着广泛的应用场景。

3.1. 内存管理函数

在C语言中,内存管理函数如malloccallocreallocfree等通常使用void*指针作为参数或返回值。因为这些函数需要处理任意类型的内存分配和释放,而void*指针的通用性使得这一点成为可能。通过void*指针,可以接收和返回任意类型的数据的内存地址,从而实现了内存管理的多态性。

#include <stdio.h>
#include <stdlib.h>int main() {// 定义一个指向整数的指针,用于后续操作数组int *intArray;int initialSize = 3;int newSize = 5;int i;// 使用 malloc 分配初始内存,用于存储 initialSize 个整数intArray = (int *)malloc(initialSize * sizeof(int));if (intArray == NULL) {fprintf(stderr, "内存分配失败,程序退出。\n");return 1;}// 初始化初始分配的内存中的数组元素for (i = 0; i < initialSize; i++) {intArray[i] = i;}// 打印初始数组元素printf("初始数组元素: ");for (i = 0; i < initialSize; i++) {printf("%d ", intArray[i]);}printf("\n");// 使用 realloc 调整内存大小,使其能存储 newSize 个整数intArray = (int *)realloc(intArray, newSize * sizeof(int));if (intArray == NULL) {fprintf(stderr, "内存重新分配失败,程序退出。\n");return 1;}// 初始化新分配的内存中的数组元素for (i = initialSize; i < newSize; i++) {intArray[i] = i;}// 打印调整大小后的数组元素printf("调整大小后的数组元素: ");for (i = 0; i < newSize; i++) {printf("%d ", intArray[i]);}printf("\n");// 使用 free 释放分配的内存free(intArray);return 0;
}

 

3.2. 泛型数据结构(链表)

在嵌入式系统中,经常需要使用各种数据结构来存储和组织数据。使用void*指针可以实现泛型数据结构,如链表、队列、栈等,这些数据结构可以存储任意类型的数据。例如,在链表节点结构体中使用void*指针来存储数据,这样链表就可以用来存储整数、浮点数、字符串或自定义结构体等多种类型的数据。这种泛型数据结构的实现提高了代码的复用性和灵活性。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>typedef struct Node {void *data;struct Node *next;
} Node;Node* createNode(size_t dataSize) {Node *newNode = (Node*)malloc(sizeof(Node));if (!newNode) {perror("Memory allocation failed for new node");exit(EXIT_FAILURE);}newNode->data = malloc(dataSize);if (!newNode->data) {perror("Memory allocation failed for node data");free(newNode);exit(EXIT_FAILURE);}newNode->next = NULL;return newNode;
}void freeNode(Node *node, size_t dataSize) {if (node) {free(node->data);free(node);}
}// 示例:添加整数节点并打印链表
void appendIntNode(Node **head, int value) {Node *newNode = createNode(sizeof(int));*(int*)newNode->data = value;if (*head == NULL) {*head = newNode;} else {Node *temp = *head;while (temp->next != NULL) {temp = temp->next;}temp->next = newNode;}
}void printIntList(Node *head) {Node *temp = head;while (temp != NULL) {printf("%d -> ", *(int*)temp->data);temp = temp->next;}printf("NULL\n");
}int main() {Node *head = NULL;appendIntNode(&head, 10);appendIntNode(&head, 20);appendIntNode(&head, 30);printIntList(head);Node *temp;while (head != NULL) {temp = head;head = head->next;freeNode(temp, sizeof(int));}return 0;
}

 

3.3. 回调函数和函数指针

在嵌入式编程中,回调函数和函数指针常用于事件处理、异步操作等场景。当回调函数的参数类型或返回值类型不确定时,可以使用void*指针来传递额外的数据。这样,回调函数就可以接收任意类型的数据作为参数,从而实现了回调函数的多态性。例如,在定时器回调函数中,可以使用void*指针来传递指向用户自定义数据结构的指针,以便在回调函数中处理这些数据。

#include <stdio.h>// 回调函数类型定义
typedef void (*Callback)(void*);// 示例回调函数
void myCallback(void *data) {int *value = (int*)data;printf("Callback received value: %d\n", *value);
}// 触发回调的函数
void triggerCallback(Callback cb, void *data) {cb(data);
}int main() {int value = 42;triggerCallback(myCallback, (void*)&value);return 0;
}

 

3.4. 跨语言调用或API接口(模拟)

在真实的跨语言调用场景中,通常会使用更复杂的机制(如FFI、JNI等)。但以下示例模拟了如何使用void*指针在C语言中模拟跨语言接口。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>// 假设这是从另一种语言传入的结构体
typedef struct ForeignStruct {void *data;size_t dataSize;
} ForeignStruct;// 处理传入结构体的函数
void processForeignStruct(ForeignStruct *fs) {if (fs->dataSize == sizeof(int)) {int *value = (int*)fs->data;printf("Processed value from foreign struct: %d\n", *value);} else {printf("Unsupported data size in foreign struct\n");}
}int main() {// 模拟从另一种语言传入的数据int foreignValue = 99;ForeignStruct fs;fs.data = &foreignValue;fs.dataSize = sizeof(int);// 处理传入的结构体processForeignStruct(&fs);return 0;
}

 

四、使用void*指针的注意事项

4.1. 类型安全问题

强制类型转换风险

  • 在使用 void* 指针时,常常需要进行强制类型转换。如果类型转换错误,会导致未定义行为。例如,将一个指向 int 类型的 void* 指针错误地转换为 float* 指针并进行操作,可能会读取或写入错误的数据。
int num = 10;
void* ptr = &num;
float* floatPtr = (float*)ptr; // 错误的类型转换
printf("%f\n", *floatPtr); // 可能输出错误结果

 

  • 解决方案:在进行类型转换前,应确保类型的正确性。可以通过额外的类型标识字段来判断,例如在结构体中添加一个 type 字段,根据该字段的值进行正确的类型转换。

缺乏编译时检查

  • void* 指针会绕过 C 语言的类型系统,编译器无法在编译时检查指针操作的类型是否匹配。可能导致在运行时才发现类型不匹配的错误,增加调试难度。
  • 解决方案:编写详细的注释和文档,明确指针的使用规则和预期类型。同时,在代码中添加适当的运行时检查,例如在函数入口处检查指针类型是否正确。

4.2. 内存管理问题

悬空指针:如果 void* 指针指向的内存被释放后,仍然使用该指针,就会形成悬空指针。使用悬空指针会导致未定义行为,可能会破坏其他数据或引发程序崩溃。

int* intPtr = (int*)malloc(sizeof(int));
void* voidPtr = intPtr;
free(intPtr);
// 此时 voidPtr 成为悬空指针
// *voidPtr = 20; // 错误操作
  • 解决方案:在释放内存后,及时将指针置为 NULL,避免误操作。例如:
int* intPtr = (int*)malloc(sizeof(int));
void* voidPtr = intPtr;
free(intPtr);
intPtr = NULL;
voidPtr = NULL;

 内存泄漏

  • 如果使用 void* 指针分配了内存,但没有正确释放,会导致内存泄漏。特别是在复杂的程序中,多个 void* 指针指向同一块内存时,容易出现重复释放或未释放的情况。
  • 解决方案:建立清晰的内存管理策略,确保每一块分配的内存都有对应的释放操作。可以使用引用计数等技术来管理内存的生命周期。

4.3. 代码可读性和可维护性问题

  • 代码复杂度增加
    • 使用 void* 指针会使代码变得复杂,尤其是在处理多个不同类型的数据时。过多的强制类型转换和类型判断会让代码难以理解和维护。
    • 解决方案:将与 void* 指针相关的操作封装成函数,减少代码中的重复和复杂性。同时,使用有意义的变量名和注释,提高代码的可读性。
  • 可移植性问题
    • 不同的编译器和平台对 void* 指针的处理可能存在差异,特别是在指针大小和对齐方式上。可能导致代码在不同平台上的行为不一致。
    • 解决方案:遵循标准 C 语言规范,避免依赖特定平台的特性。在编写代码时,进行充分的测试,确保代码在不同平台上的可移植性。

4.4. 并发访问问题

  • 如果多个线程同时访问和操作 void* 指针指向的内存,可能会导致数据竞争和不一致的问题。例如,一个线程正在释放内存,而另一个线程还在使用该指针。
  • 解决方案:使用同步机制,如互斥锁、信号量等,确保在同一时间只有一个线程可以访问和操作 void* 指针指向的内存。

4.5. 性能问题

  • 使用 void* 指针进行类型转换和类型判断会带来额外的开销,尤其是在频繁进行这些操作时。这可能会影响程序的性能。
  • 解决方案:在性能敏感的场景中,尽量减少 void* 指针的使用,或者对频繁使用的操作进行优化。例如,可以使用函数指针数组来避免类型判断和强制类型转换。

综上所述,虽然在嵌入式C语言中使用void*指针作为结构体中的“万能指针”可以实现多态性,但同时也需要仔细考虑和管理与之相关的各种风险和问题。

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

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

相关文章

NoteGen:记录、写作与AI融合的跨端笔记应用

在信息爆炸的时代,如何高效地捕捉灵感、整理知识并进行创作成为了许多人关注的问题。为此,我们开发了 NoteGen,一款专注于记录和写作的跨端 AI 笔记应用。它基于 Tauri 开发,利用其强大的跨平台能力支持 Mac、Windows 和 Linux 系统,并计划未来扩展到 iOS 和 Android 平台…

BUUCTF 蜘蛛侠呀 1

BUUCTF:https://buuoj.cn/challenges 文章目录 题目描述&#xff1a;密文&#xff1a;解题思路&#xff1a;flag&#xff1a; 相关阅读 CTF Wiki Hello CTF NewStar CTF buuctf-蜘蛛侠呀 BUUCTF&#xff1a;蜘蛛侠呀 MISC&#xff08;时间隐写&#xff09;蜘蛛侠呀 题目描述&am…

Web3 的核心理念:去中心化如何重塑互联网

Web3 是新一代互联网的构想&#xff0c;它的核心理念是去中心化&#xff0c;旨在打破传统互联网由大型平台主导的数据垄断&#xff0c;赋予用户更多的控制权和隐私保护。通过区块链技术和去中心化应用&#xff08;DApps&#xff09;&#xff0c;Web3 正在重塑互联网的运作方式。…

pyautogui操控Acrobat DC pro万能PDF转Word,不丢任何PDF格式样式

为了将PDF转换脚本改为多进程异步处理&#xff0c;我们需要确保每个进程独立操作不同的Acrobat窗口。以下是实现步骤&#xff1a; 实现代码 import os import pyautogui import time import subprocess import pygetwindow as gw from multiprocessing import Pooldef conver…

网易前端开发面试题200道及参考答案 (下)

阐述如何实现 img 按照原比例最大化放置在 div 中? 要让 img 按照原比例最大化放置在 div 中,可通过以下几种方式实现: 使用 object - fit 属性 object - fit 是 CSS 中用于规定如何调整替换元素(如 <img>、<video>)的内容以适应其容器的属性。 object - fit…

TikTok 推出了一款 IDE,用于快速构建 AI 应用

字节跳动(TikTok 的母公司)刚刚推出了一款名为 Trae 的新集成开发环境(IDE)。 Trae 基于 Visual Studio Code(VS Code)构建,继承了这个熟悉的平台,并加入了 AI 工具,帮助开发者更快、更轻松地构建应用——有时甚至无需编写任何代码。 如果你之前使用过 Cursor AI,T…

C++封装红黑树实现mymap和myset和模拟实现详解

文章目录 map和set的封装map和set的底层 map和set的模拟实现insertiterator实现的思路operatoroperator- -operator[ ] map和set的封装 介绍map和set的底层实现 map和set的底层 一份模版实例化出key的rb_tree和pair<k,v>的rb_tree rb_tree的Key和Value不是我们之前传统意…

服务器虚拟化技术详解与实战:架构、部署与优化

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在现代 IT 基础架构中&#xff0c;服务器虚拟化已成为提高资源利用率、降低运维成本、提升系统灵活性的重要手段。通过服务…

Ethflow Round 1 (Codeforces Round 1001, Div. 1 + Div. 2)(前三题)

A. String 翻译&#xff1a; 给你一个长度为 n 的字符串 s&#xff0c;其中包含 0 和/或 1。在一次操作中&#xff0c;您可以从 s 中选择一个非空的子序列 t&#xff0c;使得 t 中任何两个相邻的字符都是不同的。然后&#xff0c;翻转 t 中的每个字符&#xff08;0 变为 1&…

图漾Halcon版本SDK使用教程【V1.1.0新版本】

1.下载并安装 Halcon 1.1 下载Halcon软件 在 Halcon 官网(https://www.mvtec.com/downloads) 下载 Halcon (Windows 版) 安装包&#xff0c;并根据官方文档安装 Halcon&#xff0c;下载HALCON24.11Progress-Steady。 1.2 安装Halcon 1.解压HALCON 24.11.1.0的安装包压缩文件…

在计算机上本地运行 Deepseek R1

Download Ollama on Linux Download Ollama on Windows Download Ollama on macOS Deepseek R1 是一个强大的人工智能模型&#xff0c;在科技界掀起了波澜。它是一个开源语言模型&#xff0c;可以与 GPT-4 等大玩家展开竞争。但更重要的是&#xff0c;与其他一些模型不同&…

VS C++ 配置OPENCV环境

VS C 配置OPENCV环境 1.下载opencv2.安装环境3.opencv环境4.VS配置opencv环境5.EXE执行文件路径的环境lib和dll需要根据是debug还是release环境来区分使用哪个 6.Windows环境 1.下载opencv 链接: link 2.安装环境 双击运行即可 3.opencv环境 include文件路径:opencv\build\…

kaggle-ISIC 2024 - 使用 3D-TBP 检测皮肤癌-学习笔记

问题描述&#xff1a; 通过从 3D 全身照片 (TBP) 中裁剪出单个病变来识别经组织学确诊的皮肤癌病例 数据集描述&#xff1a; 图像临床文本信息 评价指标&#xff1a; pAUC&#xff0c;用于保证敏感性高于指定阈值下的AUC 主流方法分析&#xff08;文本&#xff09; 基于CatBoo…

SpringBoot源码解析(八):Bean工厂接口体系

SpringBoot源码系列文章 SpringBoot源码解析(一)&#xff1a;SpringApplication构造方法 SpringBoot源码解析(二)&#xff1a;引导上下文DefaultBootstrapContext SpringBoot源码解析(三)&#xff1a;启动开始阶段 SpringBoot源码解析(四)&#xff1a;解析应用参数args Sp…

Android实训九 数据存储和访问

实训9 数据存储和访问 一、【实训目的】 1、 SharedPreferences存储数据; 2、 借助Java的I/O体系实现文件的存储&#xff0c; 3、使用Android内置的轻量级数据库SQLite存储数据; 二、【实训内容】 1、实现下图所示的界面&#xff0c;实现以下功能&#xff1a; 1&#xff…

python3+TensorFlow 2.x(三)手写数字识别

目录 代码实现 模型解析&#xff1a; 1、加载 MNIST 数据集&#xff1a; 2、数据预处理&#xff1a; 3、构建神经网络模型&#xff1a; 4、编译模型&#xff1a; 5、训练模型&#xff1a; 6、评估模型&#xff1a; 7、预测和可视化结果&#xff1a; 输出结果&#xff…

《深度揭秘:TPU张量计算架构如何重塑深度学习运算》

在深度学习领域&#xff0c;计算性能始终是推动技术发展的关键因素。从传统CPU到GPU&#xff0c;再到如今大放异彩的TPU&#xff08;张量处理单元&#xff09;&#xff0c;每一次硬件架构的革新都为深度学习带来了质的飞跃。今天&#xff0c;就让我们深入探讨TPU的张量计算架构…

Queries Acceleration -Tuning- Tuning Execution 学习笔记

1 Adjustment of RuntimeFilter Wait Time 1.1 Case: Too Short RuntimeFilter Wait Time 1.1.1 没有看懂,好像是等待时间过小也会导致性能下降 1.1.2 set runtime_filter_wait_time_ms = 3000; 2 Data Skew Handling 2.1 Case 1: Bucket Data Skew Leading to Suboptimal …

React应用深度优化与调试实战指南

一、渲染性能优化进阶 1.1 精细化渲染控制 typescript 复制 // components/HeavyComponent.tsx import React, { memo, useMemo } from react;interface Item {id: string;complexData: {// 复杂嵌套结构}; }const HeavyComponent memo(({ items }: { items: Item[] }) &g…

Python3 OS模块中的文件/目录方法说明十三

一. 简介 前面文章简单学习了 Python3 中 OS模块中的文件/目录的部分函数。 本文继续来学习 OS 模块中文件、目录的操作方法&#xff1a;os.rmdir() 方法、os.stat() 方法。 二. Python3 OS模块中的文件/目录方法说明十三 1. os.rmdir() 方法 os.rmdir() 方法用于删除指定路…