OpenCASCADE开发指南<五>:OCC 内存管理器和异常类

news2024/11/19 17:33:28

  一个软件首先要规定能处理的数据类型, 其次要实现三项最基本的功能——引用管理、内存管理和异常管理。在 OCC 中,这三项功能分别对应基础类中的句柄、内存管理器和异常类。

在这里插入图片描述

1 异常类

1. 1 异常类的定义

  异常处理机制实现了正常程序逻辑与错误处理的分离, 提高了程序的可阅读性和执行效率。为了转移程序运行的控制流,异常处理的模式通常有无条件转移模式、 重试模式、 恢复模式和终止模式[5]。 与 C++一样, OCC 采用的是终止模式。

  为了实现这种异常处理机制, OCC 提供了一套异常类。所有异常类都是基于它们的根类——Failure 类的。异常类描述了函数运行期间可能发生的异常情况。发生异常时,程序将不能正常运行。对这种情况的响应被称为异常处理。

1. 2 异常类的使用

  OCC 使用异常的语法与 C++使用异常的语法相似。要产生一个确定类型的
异常,需用到相应异常类的 Raise()方法,如例 3.10 所示。

例 3.10:

DomainError::Raise(“Cannot cope with this condition”);

  这样就产生了一个 DomainError 类型的异常,同时伴有相应的提示信息“Cannot cope with this condition”。这信息可以是任意的。该异常可以被某种 DomainError 类型(DomainError 类型派生了好些类型)的句柄器捕获,如例 3.11 所示。
例 3.11:

try
{
OCC_CATCH_SIGNALS
// try 块。
}
catch (DomainError)
{
// 处理 DomainError 异常。
}

  不能把异常类的使用当作一种编程技巧, 例如用异常类代替“goto”。 应该把异常类的使用作为方法的一种保护方式(防止被错误使用),即保证方法调用者遇到的问题是方法能处理的。在程序正常运行期间,不该产生任何异常。

  在使用异常类的时候,需要用一个方法来保护另外一个可能出现异常的方
法。 这样能通过外层方法来检查内层方法的调用是否有效。 例如需要用三个方法(用于检查元素的 Value 函数、 用于检查数组下边界的 Lower 函数和用于检查数组上边界的 Upper 函数)使用 TCollection_ Array1 类,那么, Value 函数可以如例 3.12 那样被实现:

例 3.12:

Item TCollection_Array1::Value (const Standard_Integer&index) const
{
// 下面的 r1 和 r2 是数组的上下边界。
if(index < r1 || index > r2)
{
OutOfRange::Raise(“Index out of range in Array1::Value”);
}
return contents[index];
}

  在此, OutOfRange::Raise(“Index out of range in Array1::Value”)异常用 Lower函数和 Upper 函数检查索引是否有效,以保护 Value 函数的调用。

  一般地, 在 Value()函数调用前, 程序员已确定索引在有效区间内了。 这样,上面 Value()函数的实现就不是最优的了,因为检查既费时又冗余。在软件开发中有这样一种广泛的应用方式, 即将一些保护措施置于反汇编构件而非优化构件中。为了支持这种应用, OCC 为每一个异常类提供了相应的宏

Raise_if()<ErrorTypeName>_Raise_if(condition, “Error message”)

  这里 ErrorTypeName 是异常类型, condition 是产生异常的逻辑表达式,而 Error message 则是相关的错误信息。可以在编译的时候,通过 No_Exception 或者 No_两种预处理声明之一解除异常的调用,如例 3.13 所示:

例 3.13:

#define No_Exception /*解除所有的异常调用*/

使用这构造语句, Value 函数变为:

例 3.14:

Item TCollection_Array1::Value (const Standard_Integer&index) const
{
OutOfRange_Raise_if(index < r1 || index > r2,
“index out of range in Array1::Value”);
return contents[index];
}

1. 3 异常处理

  异常发生时, 控制点将转移到调用堆栈中离当前执行点最近的指定类型的句柄器上。该句柄器具有如下特征:

(1)它的 try 块刚刚被进入还没有被退出;
(2)它的类型与异常类型匹配。
(3) T 类型异常句柄器与 E 类型异常匹配,即 T 类型和 E 类型相同,或者T类型是 E 类型的超类型。

  OCC 的异常处理机制还可以将系统信号当作异常处理。为此,需要在相关代码的开端嵌入宏 OCC_CATCH_SIGNALS。建议将这个宏放在 try {}块中的第一位置。 例如, 有这样四个异常: NumericError 类型异常、 Overflow 类型异常、Underflow 类型异常和 ZeroDivide 类型异常, 其中 NumericError 类型是其它三种类型的超类型,那么,异常处理过程如例 3.15 所示。

例 3.15:

void f(1)
{
try
{
OCC_CATCH_SIGNALS
// try 块
}
catch(Standard_Overflow)
{ // 第一个句柄器
// ...
}
catch(Standard_NumericError)

}

  在这个例子中,第一个句柄器将捕获 Overflow 类型异常;第二个句柄器将
捕获 NumericError 类型异常及其派生异常,包括 Underflow 类型异常 和Zerodivide 类型异常。 异常发生时, 系统将从最近的 try 块到最远的 try 块逐一检查句柄器,直到找到一个在形式上与产生的异常相匹配的为止。

  在 try 块中, 如果将基类异常的句柄器置于派生类异常的句柄器之前, 则将发生错误。因为那样会导致后者永远不会被调用,如例 3.16 所示。
例 3.16:

void f(1)
{
int i = 0;
try
{
OCC_CATCH_SIGNALS
g(i);
// i 是可接受的。
}
// 在这放执行语句会导致编译错误!
catch(Standard_NumericError)
{
// 依据 i 值处理异常。
}
// 在这放执行语句可能导致不可预料的影响。
}

  由异常类形成的树状体系与用户定义的类完全无关。 该体系的根类是 Failure异常。 因此, Failure 异常句柄器可以捕获任何 OCC 异常。 建议将 Failure 异常句柄器设置在主路径中,如例 3.17 所示。

例 3.17:

#include <Standard_ErrorHandler.hxx>
#include <Standard_Failure.hxx>
#include <iostream.h>
int main (int argc, char* argv[ ])
{
Try
{
OCC_CATCH_SIGNALS
//主块
return 0;
}
catch(Standard_Failure)
{
Handle(Standard_Failure) error = Failure::Caught ();
cout << error << end1;
return 1;
}
}

  这里的 Caught 函数是 Failure 类的一个静态成员, 能返回一个含有异常错误信息的异常对象。这种接收对象的方法(通过 catch 的参数接收异常)代替了通常的 C++语句。

  尽管标准的 C++处理法则和语法在 try 块和句柄器中同样适用, 但在一些平台上, OCC 能以一种兼容模式被编译(此时异常支持长转移)。在这种模式中,要求句柄器前后没有执行语句。因此,强烈建议将 try 块置于{}中。此外,这种模式也要求 Standard_ErrorHandler.hxx 头文件包含在程序中(置于 try 块前), 否则将不能处理 OCC 异常。再有, catch()语句不允许将一个异常对象作为参数来传递。

  为了使程序能够像捕获其它异常那样捕获系统信号(如除零),在程序运行时要使用 OSD::SetSignal()方法安装相应的信号句柄器。通常,该方法在主函数
开端处被调用。

  为了能真正的将系统信号转换成 OCC 异常, OCC_CATCH_ SIGNALS 宏
应该被嵌入到源代码中。典型的,将该宏置于捕获异常的 try{}块的开端处。OCC 的异常处理机制依据不同的宏预处理 NO_CXX_EXCE- PTIONS 和OCC_CONVERT_SIGNALS 有不同的实现。 这些预处理将被 OCC 或者用户程序的编译程序连贯定义。在 Windows 和 DEC 平台上,这些宏不是以默认值被定义的, 并且所有类都支持 C++异常, 包括从句柄器中抛掷异常。 因此, 异常的处理与 C++异常处理一样。

2 内存管理器

2. 1 使用内存管理器的原因

  标准的内存分配有三种方式: 静态分配、 栈分配和堆分配。 静态分配是最简单的内存分配策略。 程序中的所有名字在编译时绑定在某个存储位置上; 这些绑定不会在运行时改变。块结构语言通过在栈上分配内存,克服了静态分配的一些限制。

  每次过程调用时,一个活动记录或是帧被压入系统栈,并在返回时弹出。堆分配与栈所遵循的后进先出的规律不同,堆中的数据结构能够以任意次序分配与释放。

  建模程序在运行期间,需要构造和析构大量的动态对象。在这种情况下, 标准的内存分配函数可能无法胜任工作。 因此, OCC 采用了特殊的内存管理器(在Standard 包中实现)。

2.2 内存管理器的用法

  在 C 代码中使用 OCC 内存管理器分配内存,只需用 Standard::Allocate()方法代替 malloc() 函数, Standard::Free() 方法代替 free() 函数,以 及
Standard::Reallocate()代替 realloc()函数。

  在 C++中, 可以将类的 new()操作定义为使用 Standard::Allocate()方法进行内存分配, 而将类的 delete()操作定义为使用 Standard::Allocate()方法进行内存释放。这样就可以使用 OCC 内存管理器为所有对象和所有类分配内存。就是用这种方式, CDL 提取器为所有用 CDL 声明的类定义了 new()函数和 delete()函数。 因此,除了异常类,所有 OCC 类都使用 OCC 内存管理器进行存储分配。

  因为 new()函数和 delete()函数是被继承的,所以对于所有 OCC 类的派生类(比如, Standard_Transient 类的派生类), new()函数和 delete()函数同样适用。

2.3 内存管理器的配置

  OCC 内存管理器可以适用于不同的内存优化技术(不同的内存块采用不同的优化技术, 这主要依据内存块的大小而定)。 或者, 用 OCC 内存管理器, 甚至可以不采用任何优化技术而直接使用 C 函数 malloc() 和 free()。

  内存管理器的配置由下面几个环境变量值定义:

(1) MMGT_OPT。如果值设为 1(默认值),则内存管理器将如下面的描述那样对内存进行优化。 如果值设为 0, 则每个内存块将直接分配(通过 malloc()和 free()函数)在 C 内存堆里。在第二种情况下, 所有异常(不包括 MMGT_CLEAR异常)都将被忽略。

(2) MMGT_CLEAR。 如果值设为 1(默认值), 则每一个已分配的内存块都将被清零。如果值设为 0,则内存块正常返回。

(3) MMGT_CELLSIZE。 它定义了大内存池中内存块的最大空间。 默认值是 200 字节。
(4) MMGT_NBPAGES。它定义了页面中由小内存块构成的内存组件(内存池的大小(由操作系统决定)。默认值是 1000 字节。

(5) MMGT_THRESHOLD。它定义了内存块(能直接在 OCC 内部被循环使用)的最大空间。默认值是 40000 字节。

(6) MMGT_MMAP。 当值设为 1(默认值) 时, 使用操作系统的映射函数对大内存块进行分配。 当值设为 0 时, 大内存块将被 malloc()分配在 C 内存堆里。

(7) MMGT_REENTRANT。 当值设为 1 时, 所有对内存优化管理器的调用都将被响应, 以保证不同的线程能同时访问内存管理器。 在多线程程序中, 这个变量值应该设置为 1。这里所说的多线程程序是指那些使用 OCC 内存管理器,并且可能有不止一个调用 OCC 函数的线程的程序。默认值是 0。

  在此提一个注意事项: 当多线程程序使用 OCC 以达到最佳内存优化性能时,需要检查两组变量。 其中一组是 MMGT_OPT=0; 另一组则是 MMGT_OPT=1 和
MMGT_REENTRANT=1。

2.4 内存管理器的实现

  当且仅当 MMGT_OPT=1 时, 才用到 OCC 的特殊的内存优化技术。 这些技术有:

(1) 小内存块( 空间比由 MMGT_CELLSIZE 设定的值小) 不能单独分配,而是分配在大内存池(大小由变量 MMGT_NBPAGES 决定)中。每一个内存块分配在当前内存池的空闲部分。若内存池被完全占据,则使用下一个内存池。 在当前版本中,在进程结束前,内存池不能返回操作系统。然而,那些由 Standard::Free()释放的内存块被记忆在释放列表中。 当需要下一个内存块(与列表中某个块大小相同) 时, 相应的被释放的那个内存块所占空间可以被新的内存块占用,这也叫内存块的循环使用。

( 2)对于中等大小的内存块(比 MMGT_CELLSIZE 大,但比 MMGT_THRESHOLD 小),它们是被直接分配(通过使用 malloc() 和 free())在C内存堆里。 这些块要是被 Standard::Free()方法释放的话, 可以像小内存块那样被循环使用。 然而, 与小内存块不同, 那些被记录在释放列表中的可循环使用的中内存块(由持有内存管理器的程序释放)可以被 Standard::Purge()方法返回到 C 内存堆中。

( 3)大内存块(大小比 MMGT_THRESHOLD 大,包括用来分配小内存块的内存池) 的分配取决于 MMGT_MMAP 的值。 如果该值是 0, 则这些大块被分配在 C 堆里。否则,它们被操作系统映射函数分配在内存映射文件中。 当Standard::Free()被调用时,大块立即被返回到操作系统中去。

2.5 内存管理器的优缺点

  OCC 内存管理器的优点主要体现在小块和中块的循环使用上。当程序需要连续分配和释放大小差不多的内存块时, 这个优点能加速程序的执行。 实际应用中,这种提升幅度可以高达 50%。

  相应的, OCC 内存管理器的主要缺点是:程序运行时,被循环使用的内存块不能返回到操作系统中。 这可能导致严重的内存消耗, 甚至会被操作系统误认为内存泄露。为了减少这种影响,在频繁地对内存进行操作后, OCC 系统将调用 Standard::Purge()方法。

  另外, OCC 内存管理器会带来额外的开销,它们有:

(1)舍入后,每个被分配的内存块的大小高达 8 字节。当 MMGT_OPT=0时,舍入值由 CRT 决定;对 32 位平台而言,典型值是 4 字节。

( 2) 在每个内存块的开端需要额外的 4 字节以记录该内存块的大小( 或者,当内存块被记录在释放列表时, 这 4 字节用来记录下一个内存块的地址)。 注意:只有在 MMGT_OPT=1 时,才需要这 4 字节。

  需要注意的是: 由 OCC 内存管理器带来的额外开销可能比由 C 内存堆管理器带来的额外开销大, 或者小。 因此, 整体而言, 很难说到底是优化模式的内存消耗大还是标准模式的内存消耗大——这得视情况而定。

  通常, 编程人员自己也会采用一种优化技术——在内存里面划出一些重要的块。 这样就可以将一些连续的数据存于这些块中, 使内存页面管理器对这些块的处理变得更容易。

  在多线程模式(MMGT_REENTRANT=1) 中, OCC 内存管理器使用互斥机制以锁定程序对释放列表的访问。 因此, 当不同的线程经常同时调用内存管理器时,优化模式的性能不如标准模式的性能好。原因是: malloc() 函数和 free()函数在实现的过程中开辟了几个分配空间——这样就避免了由互斥机制带来的延迟。

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

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

相关文章

淘宝基于Nginx二次开发的Tengine服务器

最近在群里看到这样一张阿里云网关报错的截图&#xff0c;我保存下来看了下 看到下面有 Tengine提供技术支持&#xff0c;这个Tengine是什么东西呢&#xff1f;我搜索了下似乎是淘宝在nginx的基础上自己改的Web服务器 Tengine还支持OpenResty框架&#xff0c;该框架是基于Ngin…

与鲸同行,智领未来!和鲸科技高校市场渠道合作伙伴正式开启招募

AI 浪潮来袭&#xff0c;技术日新月异&#xff0c;校企合作已成为高校培养符合产业需求的应用型人才、加速科研创新与成果转化的关键途径。从单一应用到多元化布局&#xff0c;各企业更需要技术领先、战略协同的领域伙伴协力共进。 和鲸科技以“协同平台实践社区竞赛”三位一体…

26 easy 35. 搜索插入位置

//给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 // // 请必须使用时间复杂度为 O(log n) 的算法。 // // // // 示例 1: // // //输入: nums [1,3,5,6], …

算法提高之楼兰图腾(树状数组)

楼兰图腾(树状数组) 核心算法&#xff1a;树状数组 将下标转化为二进制 例如11100100 父节点下标x 子节点下标i 由下图可知 每一个数都可以由其子节点**(如果有)**求和得到**由父节点找子节点&#xff1a;**每个子节点下标 –> x – 1 – lowbit(x – 1)由子节点找父节点&am…

19.创建帖子

文章目录 一、建立路由二、开发CreatePostHandler三、编写logic四、编写dao层五、编译测试运行 一、建立路由 这里要稍微注意的是&#xff1a;需要登录后才可以发表帖子&#xff0c;所以需要用到我们之前写的鉴权中间件。中间件对用户携带的token解析成功后&#xff0c;便会将…

深度解析Nginx正向代理的原理与实现

目录 前言 1. 什么是正向代理 2. Nginx正向代理的配置 3. Nginx正向代理的实现原理 4. 示例代码 5. 总结 前言 Nginx是一个高性能的Web服务器和反向代理服务器&#xff0c;但它也可以用作正向代理服务器。本文将深入解析Nginx正向代理的原理和实现&#xff0c;并提供相关…

排序算法之快速排序算法介绍

目录 快速排序介绍 时间复杂度和稳定性 代码实现 C语言实现 c实现 java实现 快速排序介绍 快速排序(Quick Sort)使用分治法策略。 它的基本思想是&#xff1a;选择一个基准数&#xff0c;通过一趟排序将要排序的数据分割成独立的两部分&#xff1b;其中一部分的所有数据…

市场复盘总结 20240313

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 昨天选出的个股&#xff0c;今日涨6.87% 二…

Vue+SpringBoot打造创意工坊双创管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 管理员端2.2 Web 端2.3 移动端 三、系统展示四、核心代码4.1 查询项目4.2 移动端新增团队4.3 查询讲座4.4 讲座收藏4.5 小程序登录 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的创意工坊双创管理…

JUC之JMM

Java内存模型JMM JMM三大特性&#xff1a;可见性、有序性、原子性 可见性 原子性&#xff1a;指一个操作是不可打断的&#xff0c;即多线程的环境下&#xff0c;操作不能被其他线程干扰 有序性 处理器在进行重排序时&#xff0c;必须要考虑指令之间的数据依赖性。多线程环境…

一款开箱即用的 Vue3 中后台管理系统框架

概述 Fantastic-admin是一个基于Vue.js技术栈的后台管理框架&#xff0c;设计用于提升开发效率和优化用户界面。它通常包括了一套完整的前端解决方案&#xff0c;如用户界面组件、工具和库&#xff0c;以支持快速开发高质量的后台产品。这个框架可能强调易用性、灵活性和高性能…

思科网络中如何进行动态NAT配置

一、什么是动态NAT&#xff1f;动态NAT与静态NAT的区别是什么&#xff1f; &#xff08;1&#xff09;动态NAT&#xff08;Network Address Translation&#xff09;是一种网络地址转换技术&#xff0c;它会动态地将内部私有网络中的局域网IP地址映射为公共IP地址&#xff0c;…

String、StringBuilder、StringBuffer 有什么区别?

1、典型回答 String、StringBuilder 和 StringBuffer 都是 Java 语言中&#xff0c;用于操作字符串的类&#xff0c;但它们在性能、可变性和线程安全性方面有一些区别 1、String&#xff1a;不可变字符串类&#xff0c;也就是说一旦创建&#xff0c;它的值就不可变。每次对 S…

基于java+springboot+vue实现的停车场管理系统(文末源码+Lw)23-258

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统停车场管理系统信息管理难度大&#xff0c;容错率低&…

HarmonyOS NEXT应用开发之深色跑马灯案例

介绍 本示例介绍了文本宽度过宽时&#xff0c;如何实现文本首尾相接循环滚动并显示在可视区&#xff0c;以及每循环滚动一次之后会停滞一段时间后再滚动。 效果图预览 使用说明&#xff1a; 1.进入页面&#xff0c;检票口文本处&#xff0c;实现文本首尾相接循环滚动&#x…

创建空的Numpy数组

参考&#xff1a;Creating Empty Numpy Array Numpy 是一个开源的 Python 扩展库&#xff0c;用于科学计算和数据分析。它提供了高性能的多维数组对象&#xff0c;以及在这些数组上进行的各种操作。 在 Numpy 中&#xff0c;可以使用 numpy 模块的 empty 函数来创建一个空的 …

Vue3全家桶 - Vue3 - 【1】前置准备和介绍(VsCode插件 + 组合式API和选项式API的比较)

一、前言 Vue2.7是当前、同时也是最后一个 Vue2.x 的次级版本更新。Vue2.7 会以其发布日期&#xff0c;即2022年7月1日开始计算&#xff0c;提供18个月的长期技术支持。在此期间&#xff0c;Vue2将会提供必要的bug修复和安全修复。但不再提供新特性。Vue2的终止支持时间是2023…

C++ 有边数限制的最短路 Bellman_ford算法(带负权边)

给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c; 边权可能为负数。 请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离&#xff0c;如果无法从 1 号点走到 n 号点&#xff0c;输出 impossible。 注意&#xff1a;图中可能 存在负权回路…

算法刷题Day4 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II

目录 0 引言1 两两交换链表中的节点1.1 我的解题1.2 注意事项 2 删除链表的倒数第N个节点2.1 我的代码2.2 报错原因分析 3 链表相交3.1 我的解题 4 环形链表II4.1 我的解题 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标…

10个让你事半功倍的工作效率技巧,让你成为高效率的现代人!

作为现代人&#xff0c;我们每天都在面对各种各样的任务和工作&#xff0c;有时候会因为繁忙而感到力不从心&#xff0c;导致效率变得非常低下。所以&#xff0c;在这篇文章中&#xff0c;我将分享10个能够帮助你提高工作效率的技巧&#xff0c;让你的工作事半功倍。 1. 制定计…