学习open62541 --- [76] 使用智能指针处理内存释放问题

news2024/11/17 13:23:33

在使用监测项时,一般都会加一个context,然后在回调函数里使用这个context,这就需要保证context的内存空间在执行回调函数时是有效的。往往有以下三种方法:

  1. 使用静态内存空间:使用static创建静态变量,然后把变量地址当做context,缺点是一个静态变量只能用于一个监测项,函数无法重用(重用会造成多个监测项使用同一个静态变量)
  2. 使用局部内存空间:保证局部变量一直有效就行,例如局部变量定义在main函数里,因为main函数一直在栈上,所以这个局部变量会一直有效。这个只能用于简单项目。
  3. 使用动态内存:在创建监测项时直接动态分配内存,比较简单,就是释放内存比较费劲

一般来说,都是使用第三个方法。本文先举例讲解第三个方法,最后讲述如何使用智能指针去解决释放问题。


一 例子

下面是个简单的监测项例子,server端添加一个变量,然后创建监测项去监测该变量,再添加一个定时任务去每隔2s把该变量的值加1,从而可以触发监测的回调函数,

// server.cpp
#include <memory>

#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


#include "open62541.h"

UA_Boolean running = true;

void stopHandler(int sign) {
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

UA_UInt32 monid = 0;


UA_NodeId addTheAnswerVariable(UA_Server *server) 
{
    /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 1;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
    attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)"the answer");
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /* Add the variable node to the information model */
    UA_NodeId theAnswerNodeId = UA_NODEID_NUMERIC(1, 62541);
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, (char*)"the answer");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_Server_addVariableNode(server, theAnswerNodeId, parentNodeId,
                              parentReferenceNodeId, myIntegerName,
                              UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL);
    
    return theAnswerNodeId;
}


void dataChangeNotificationCallback(UA_Server *server, UA_UInt32 monitoredItemId,
                               void *monitoredItemContext, const UA_NodeId *nodeId,
                               void *nodeContext, UA_UInt32 attributeId,
                               const UA_DataValue *value) 
{
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Received Notification");
    
    UA_NodeId * targetNodeId = (UA_NodeId*)monitoredItemContext;

    if (monitoredItemId == monid && UA_NodeId_equal(nodeId, targetNodeId))
    {
        UA_Int32 currentValue = *(UA_Int32*)(value->value.data);
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Current Value: %d\n", currentValue);
        
    }
}


UA_UInt32 addMonitoredItemToVariable(UA_Server *server, UA_NodeId TargetNodeId) 
{
    UA_MonitoredItemCreateResult result;
    
    UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default(TargetNodeId);
    
    monRequest.requestedParameters.samplingInterval = 100.0; // 100 ms interval
    
    // 使用动态内存
    UA_NodeId * pContext = (UA_NodeId *)UA_malloc(sizeof(UA_NodeId));
    UA_NodeId_copy(&TargetNodeId, pContext);

    result = UA_Server_createDataChangeMonitoredItem(server, UA_TIMESTAMPSTORETURN_BOTH,
                                            monRequest, (void*)pContext, 
                                            dataChangeNotificationCallback);
    
    if (result.statusCode == UA_STATUSCODE_GOOD)
    {
        
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, OK.");
        return result.monitoredItemId;
    }
    else
    {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, Fail.");
        return 0xFFFFFFFF;
    }
}


void cycleCallback(UA_Server *server, void *data)
{
    static UA_Int32 update = 2;

    UA_NodeId *targetNodeId = static_cast<UA_NodeId*>(data);

    UA_Variant myVar;
    UA_Variant_init(&myVar);
    UA_Variant_setScalar(&myVar, &update, &UA_TYPES[UA_TYPES_INT32]);
    UA_Server_writeValue(server, *targetNodeId, myVar);

    if (update++ == 1000)
    {
        update = 1;
    }
}


int main(void) 
{    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    UA_NodeId targetNodeId = addTheAnswerVariable(server);


    monid = addMonitoredItemToVariable(server, targetNodeId);
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Monitored item id: %d\n", monid);


    UA_UInt64 callbackId = 0;
    UA_Server_addRepeatedCallback(server, cycleCallback, &targetNodeId, 2000, &callbackId); // call every 2s



    UA_StatusCode retval = UA_Server_run(server, &running);

    UA_Server_delete(server);

    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

PS: 所添加的变量,其nodeid是数字形式的,不能是string类型的

context的内存分配是在函数addMonitoredItemToVariable()里。

编译运行,一切OK,如下,回调函数也正常运行
在这里插入图片描述
但是,如果使用Valgrind去检查,就会发现有内存泄露,命令如下,demo是本人的可执行文件名字,

valgrind --tool=memcheck --leak-check=full ./demo

启动后,等回调函数执行之后按Ctrl+c结束,然后会给出分析结果,
在这里插入图片描述
可以看到有24个字节的内存泄露,UA_NodeId占用的大小就是24个字节。

根本原因是我们动态分配了内存之后,没有释放,虽然最终程序结束会释放内存,但如果在程序运行时,我们删除这个监测项而没有释放对应的内存,那么就会内存泄露。


二 使用智能指针

如果项目使用C++开发的,那么就可以使用shared_ptr来解决释放问题。重新编写addMonitoredItemToVariable(),如下,

std::shared_ptr<UA_NodeId> addMonitoredItemToVariable(UA_Server *server, UA_NodeId TargetNodeId, UA_UInt32& monitoredItemId) 
{
    UA_MonitoredItemCreateResult result;
    
    UA_MonitoredItemCreateRequest monRequest = UA_MonitoredItemCreateRequest_default(TargetNodeId);
    
    monRequest.requestedParameters.samplingInterval = 100.0; // 100 ms interval

    // UA_NodeId * pContext = (UA_NodeId *)UA_malloc(sizeof(UA_NodeId));
    // UA_NodeId_copy(&TargetNodeId, pContext);
    std::shared_ptr<UA_NodeId> spContext = std::make_shared<UA_NodeId>();
    UA_NodeId_copy(&TargetNodeId, spContext.get());

    result = UA_Server_createDataChangeMonitoredItem(server, UA_TIMESTAMPSTORETURN_BOTH,
                                            monRequest, (void*)spContext.get(), 
                                            dataChangeNotificationCallback);
    
    if (result.statusCode == UA_STATUSCODE_GOOD)
    {
        
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, OK.");
        monitoredItemId = result.monitoredItemId;
    }
    else
    {
        UA_LOG_ERROR(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Add monitored item for variable, Fail.");
        monitoredItemId = 0xFFFFFFFF;
    }

    return spContext;
}

函数里使用spContext 来指向context内存,最后返回这个智能指针。

main函数如下,创建了一个holdSP来承接addMonitoredItemToVariable()的返回值,有了这个holdSP,那么智能指针指向的内存就不会被释放

int main(void) 
{    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_Server *server = UA_Server_new();
    UA_ServerConfig_setDefault(UA_Server_getConfig(server));

    UA_NodeId targetNodeId = addTheAnswerVariable(server);

    
    std::shared_ptr<UA_NodeId> spContext;
    monid = addMonitoredItemToVariable(server, targetNodeId, spContext);
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Monitored item id: %d\n", monid);
    

    UA_UInt64 callbackId = 0;
    UA_Server_addRepeatedCallback(server, cycleCallback, &targetNodeId, 2000, &callbackId); // call every 2s



    UA_StatusCode retval = UA_Server_run(server, &running);

    UA_Server_delete(server);

    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

然后重新编译运行,依然一切OK,最后使用Valgrind来分析内存,
在这里插入图片描述

可以看出没有内存泄漏了。


三 总结

本文讲述如何使用智能指针来存放context内存,使用完后只要再创建一个智能指针存放一下,这样当程序结束时就会自动释放,不用手动去释放,这样就不会担心忘记内存泄漏了。

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

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

相关文章

【Python 爬虫常见的报错及其解决方法】零基础也能轻松掌握的学习路线与参考资料

Python 爬虫被广泛应用于数据采集和分析。然而&#xff0c;爬虫在运行过程中常常会遇到各种问题和错误&#xff0c;降低了爬虫效率、准确性和可靠性。因此掌握爬虫常见报错及其解决方法是非常关键的。本文将介绍 Python 爬虫常见的报错及其解决方法&#xff0c;并提供参考资料和…

内存泄漏的原因,内存泄漏如何避免?内存泄漏如何定位?

1. 内存溢出 内存溢出 OOM &#xff08;out of memory&#xff09;&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现out of memory&#xff1b;比如申请了一个int,但给它存了long才能存下的数&#xff0c;那就是内存溢出。 2. 内存泄…

PyTorch LSTM和LSTMP的原理及其手写复现

PyTorch LSTM和LSTMP的原理及其手写复现 0、前言全部参数的细致介绍代码实现Reference 0、前言 关于LSTM的原理以及公式其实在这篇博客一步一步详解LSTM网络【从RNN到LSTM到GRU等&#xff0c;直至attention】讲的非常清晰明了了。 这里就是写出LSTM的pytorch的实现&#xff0c;…

【随笔记】全志 T507 PF4 引脚无法被正常设置为中断模式的问题分析

相关信息 硬件平台&#xff1a;全志T507 系统版本&#xff1a;Android 10 / Linux 4.9.170 问题描述&#xff1a;PF4 无法通过标准接口设置为中断模式&#xff0c;PF1、PF2、PF3、PF5 都可以。 分析过程 一开始以为是引脚被其它驱动占用引起&#xff0c;或者该引脚不具备中断…

高光谱成像技术在果蔬品质检测中的应用

在当前市场经济背景下&#xff0c;食品安全问题是消费者最为关心的问题之一&#xff0c;尤其是果蔬产品&#xff0c;农药残留问题和品质问题直接关系着消费者的权益和人身安全。针对传统化学检测的缺陷&#xff0c;本文结合高光谱成像技术&#xff0c;对其在果蔬品质与安全无损…

【C++】多态的概念/重写/虚表/抽象类

多态 多态的概念多态的定义和实现重写抽象类多态的原理虚表的构建原理虚函数的调用原理 多态的概念 多态就是多种形态&#xff0c;传递不同的对象&#xff0c;会调用不同的方法。 多态的定义和实现 那么在C语法中&#xff0c;多态是如何实现的呢&#xff1f; 我们首先要在继承…

vue学习 - 基础篇

初始工程结构 这里我们使用script标签从cdn获取vue.js, 而不是使用脚手架vue-cli, 因为cdn比较方便一点, 也不用配置node之类的比较麻烦 index.html <!DOCTYPE html> <html><head><title>VueJS Course</title><link rel"stylesheet"…

第三篇、基于Arduino uno,用oled0.96寸屏幕显示dht11温湿度传感器的温度和湿度信息——结果导向

0、结果 说明&#xff1a;先来看看拍摄的显示结果&#xff0c;如果是你想要的&#xff0c;可以接着往下看。 1、外观 说明&#xff1a;本次使用的oled是0.96寸的&#xff0c;别的规格的屏幕不一定适用本教程&#xff0c;一般而言有显示白色、蓝色和蓝黄一起显示的&#xff0…

RabbitMQ日常使用小结

一、使用场景 削峰、解耦、异步。 基于AMQP(高级消息队列协议)协议来统一数据交互,通过channel(网络信道)传递信息。erlang语言开发&#xff0c;并发量12000&#xff0c;支持持久化&#xff0c;稳定性好&#xff0c;集群不支持动态扩展。 RabbitMQ的基本概念 二、组成及工作流…

可见性原子性有序性的+线程传参的方式+Java如何实现多个线程之间共享数据+线程间通信+死锁产生

//为了均衡CPU和内存的速度差异,增加了缓存 导致了可见性的问题; //操作系统增加了进程 线程 分时复用CPU,均衡CPU和io设备的速速差异 导致了原子性问题; //jvm指令重排序(优化指令排序) 导致了有序性的问题 可见性问题是指 线程A修改共享变量,修改后CPU缓存中的数据没有及时同…

Emacs之目前最快补全插件lsp-bridge(八十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

数据分析12——Pandas中数据合并方法

0、前言&#xff1a; 在pandas中进行数据合并的操作和数据库中的join操作非常类似。 1、merge横向合并&#xff1a; 前言&#xff1a;该函数只能做横向合并函数名&#xff1a;merge()函数参数&#xff1a; left: 数据类型为’DataFrame | Series’&#xff0c;需要进行合并的…

[CTF/网络安全] 攻防世界 PHP2 解题详析

[CTF/网络安全] 攻防世界 PHP2 解题详析 index.php.phps扩展名姿势 翻译&#xff1a;你能给这个网站进行身份验证吗&#xff1f; index.php index.php是一个常见的文件名&#xff0c;通常用于Web服务器中的网站根目录下。它是默认的主页文件名&#xff0c;在访问一个网站时&am…

说说计算这事儿:从开关到人工智能

目录 一 前言 二 计算历史 三 计算探秘 四 算力优化 五 未来展望 一 前言 计算本身其实是一个比较抽象的词&#xff0c;或者说比较笼统。很多场景都可能用到计算这个词&#xff0c;因此具体的含义就需要根据上下文来确定。今天我们讨论的计算&#xff0c;是比较狭义的计算…

【环境准备】在虚拟机的Ubuntu下安装VS Code并配置C/C++运行环境

1.点击进入 vscode官网 下载.deb安装包 2.启动虚拟机下的Ubuntu&#xff0c;Windows下的Xftp和Xshell Xftp&#xff1a;用于将刚刚在Windows下下载好的vscode.deb安装包传输到Ununtu中。Xshell&#xff1a;用于远程登录Ununtu&#xff0c;进行 vscode.deb 安装包安装&#xff…

算法26:递归练习

目录 题目1&#xff1a;给你一个字符串&#xff0c;要求打印打印出这个字符串的全部子序列&#xff08;子序列不能重复&#xff09; 题目2&#xff1a;打印一个字符串的全部排列。 题目3&#xff1a;针对题目2&#xff0c;要求去除重复元素 题目4&#xff1a;给定一个字符串…

ARM的读写内存指令与栈的应用

1.基础读写指令 写内存指令&#xff1a;STR MOV R1, #0xFF000000 MOV R2, #0x40000000 STR R1, [R2] 将R1寄存器中的数据写入到R2指向的内存空间 需注意&#xff0c;此命令是将R1中的数据写给R2所指向的内存空间&#xff0c;而不是直接把R1的数据赋给R2&#xff0c;R2寄存器…

chatgpt赋能Python-python3_9如何安装

Python 3.9 安装教程 Python 是一款非常流行的编程语言&#xff0c;而 Python 3.9 是其中的最新版本。不过&#xff0c;有些人可能会遇到一些问题&#xff0c;因为这是一个新版本。在本篇文章中&#xff0c;我们将介绍 Python 3.9 的安装过程&#xff0c;并提供一些关键的步骤…

无线通信网 - 动态主机配置协议 DHCP

文章目录 1 概述2 DHCP2.1 工作原理2.2 报文类型 3 扩展3.1 网工软考真题 1 概述 #mermaid-svg-VTnvU3Vd01Y4gppz {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-VTnvU3Vd01Y4gppz .error-icon{fill:#552222;}#merm…

[CTF/网络安全] 攻防世界 Training-WWW-Robots 解题详析

[网络安全] 攻防世界 Training-WWW-Robots 解题详析 在这个小训练挑战中&#xff0c;你将学习 Robots_exclusion_standard&#xff08;机器人排除标准&#xff09;。 robots.txt 文件是由网络爬虫用来检查是否允许他们爬行和索引你的网站或仅部分内容。有时这些文件揭示目录结构…