(delphi11最新学习资料) Object Pascal 学习笔记---第13章第6节 (嵌套的Finally代码块)

13.6.2 嵌套的Finally代码块

​ Finally代码块可能是确保程序安全最重要、最常用的技术。我不认为这是一个高级话题,但你是否在所有地方都使用了 finally?在边界情况下,例如嵌套操作中,你是否正确使用了finally,还是在一个finally块中合并了多个finalization语句?这是一个远非完美的代码示例:

procedure TForm1.BtnTryFClick(Sender: TObject);
var
  A1, A2: TAClass;
begin
  A1 := TAClass.Create;
  A2 := TAClass.Create;
  try
    A1.Whatever := 'One';
    A2.Whatever := 'Two';
  finally
    A2.Free;
    A1.Free;
  end;
end;

​ 这是相同代码更安全,更正确的版本(再次从SafeCode示例中提取):

procedure TForm1.BtnTryFClick(Sender: TObject);
var
  A1, A2: TAClass;
begin
  A1 := TAClass.Create;
  try
    A2 := TAClass.Create;
    try
      A1.Whatever := 'One';
      A2.Whatever := 'Two';
    finally
      A2.Free;
    end;
  finally
    A1.Free;
  end;
end;
13.6.3 动态类型检查

​ 一般来说,类型之间的动态转换操作,尤其是类类型之间的动态转换操作,是另一个陷阱可能的来源。特别是如果您不使用 is 和 as 操作符,而只是进行硬类型转换。事实上,每一次直接类型转换都是潜在的错误源(除非它遵循 is 检查)。

​ 从对象到指针、从类引用到类引用、从对象到接口、从字符串到字符串的类型转换可能非常危险,但在某些特殊情况下很难避免。例如,你可能想在组件的 Tag 属性中保存对象引用,而 Tag 属性是一个整数,因此你不得不进行硬转换。另一种情况是使用老式的 TList(而不是类型安全的泛型列表,将在下一章介绍)将对象保存在指针列表中。

​ 这里有一个相当愚蠢的例子:

procedure TForm1.BtnCastClick(Sender: TObject);
var
  List: TList;
begin
  List := TList.Create;
  try
    List.Add(Pointer(Sender));
    List.Add(Pointer(23422));
    // 直接类型转换
    TButton(List[0]).Caption := 'Ouch';
    TButton(List[1]).Caption := 'Ouch';
  finally
    List.Free;
  end;
end;

​ 运行这段代码通常会导致访问违规。

注解:这里我说 "一般 "是因为当你随机访问内存时,你永远不知道实际效果如何。有时,程序只是覆盖了内存,并不会立即导致错误,但事后你就很难弄清楚为什么其他数据会被破坏。

​ 你应该尽可能避免类似情况的发生,但如果你碰巧别无选择,又该如何修复这段代码呢?最自然的方法是使用as安全类型转换(as safe cast)或is类型检查(is type check),就像下面的代码片段一样:

// "as"类型转换
(TObject(List[0]) as TButton).Caption := 'Ouch';
(TObject(List[1]) as TButton).Caption := 'Ouch';
// "is"类型转换
if TObject(List[0]) is TButton then
  TButton(List[0]).Caption := 'Ouch';
if TObject(List[1]) is TButton then
  TButton(List[1]).Caption := 'Ouch';

​ 然而,这并不是解决问题的办法,你会继续遇到访问违规。问题在于,is 和 as 最终都会调用 TObject.InheritsFrom,而这是一种很难在数字上执行的操作!

​ 解决办法是什么?真正的解决办法是首先避免出现类似情况(老实说,这类代码没有什么意义),例如使用 TObjectList 或其他安全技术(再次参阅下一章的通用容器类)。如果你真的喜欢低级黑客并喜欢玩指针,你可以试着找出给定的 "数字值 "是否真的是一个对象的引用。不过,这并不是一个简单的操作。这其中还有有趣的一面,我将以此为借口在下面的演示中解释对象和类引用的内部结构。

13.6.4 这个指针是对象引用吗

​ 本节解释了对象和类引用的内部结构,远远超出了本书大部分内容的讨论范围。尽管如此,它仍能为更专业的读者提供一些有趣的见解,因此我决定保留这部分内容,这些内容来自我以前写过一篇关于内存管理的高级论文。还要注意的是,下面内存检查方面的具体实现只是适用于 Windows系统。

​ 有时,你会有一些指针(指针只是一个数值,指的是某些数据的物理内存位置)。这些指针实际上可能是对对象的引用,你通常知道它们是什么时候的引用,并将它们作为引用使用。但是,每当你进行一次低层次的转换时,你就真的快要把整个程序搞砸了。有一些技术可以让这种指针管理更安全一些,即使不能保证百分之百安全。

​ 在使用指针之前,你可能需要考虑的问题是,它是否是一个合法的指针。Assigned 函数只能检查指针是否为 nil,在这种情况下并没有帮助。不过,Object Pascal RTL(Windows 平台上的 System 单元)中鲜为人知的 FindHInstance 函数会返回堆块的基地址,其中包括作为参数传递的对象,如果指针指向的是无效页,则返回 0(防止出现频率很低但极难跟踪的内存页错误)。如果随便取一个数字,它很可能不是指向一个有效的内存页。

​ 这是一个很好的起点,但我们可以做得更好,因为如果值是字符串引用或任何其他有效指针,而不是对象引用,这样做也无济于事。现在,如何知道指针是否真的是对象引用呢?我想出了以下经验测试方法。对象的前 4 个字节是指向其类的指针。如果考虑类引用的内部数据结构,那么它的 vmtSelfPtr 位置就是指向自身的指针。如图 13.7 所示。

图 13.7:对象和类引用内部结构的大致示意图

​ 换句话说,从类引用指针(这是一个负偏移量,在内存中较低的位置)解引用内存位置 vmtSelfPtr 字节上的值,就可以再次获得相同的类引用指针。此外,在类引用的内部数据结构中,可以读取实例大小信息(位于 vmtInstanceSize 位置),看看其中是否有合理的数字。以下是实际代码:

function IsPointerToObject(Address: Pointer): Boolean;
var
  ClassPointer, VmtPointer: PByte;
  InstSize: Integer;
begin
  Result := False;
  if FindHInstance(Address) > 0 then
  begin
    VmtPointer := PByte(Address^);
    ClassPointer := VmtPointer + vmtSelfPtr;
    if Assigned(VmtPointer) and (FindHInstance(VmtPointer) > 0) then
    begin
      InstSize := (PInteger(VmtPointer + VmtInstanceSize))^;
      // 检查Self指针和“合理”的实例大小
      if Pointer(Pointer(ClassPointer)^ = Pointer(VmtPointer)) and
         (InstSize > 0) and (InstSize < 10000) then
        Result := True;
    end;
  end;
end;

注解 此函数返回正确值的概率非常高,但并非百分之百。不幸的是,内存中的随机数据可能会通过测试。

​ 有了这个函数,在前面的 SafeCode 示例中,我们可以在进行安全转换之前添加一个指针到对象的检查:

if IsPointerToObject(List[0]) then
  (TObject(List[0]) as TButton).Caption := 'Ouch';
if IsPointerToObject(List[1]) then
  (TObject(List[1]) as TButton).Caption := 'Ouch';

​ 同样的想法也可以直接应用于类引用,以实现它们之间的安全转换。同样,最好首先通过编写更安全、更简洁的代码来避免类似问题,但万一无法避免,IsPointerToObject 函数可能会派上用场。无论如何,本节应该已经解释了这些系统数据结构的一些内部构造。

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

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

相关文章

SpringBoot打war包并配置外部Tomcat运行

简介 由于其他原因&#xff0c;我们需要使用SpringBoot打成war包放在外部的Tomcat中运行,本文就以一个案例来说明从SpringBoot打war包到Tomcat配置并运行的全流程经过 环境 SpringBoot 2.6.15 Tomcat 8.5.100 JDK 1.8.0_281 Windows 正文 一、SpringBoot配置打war包 第一步&a…

怎么通过互联网远程控制电脑?

远程访问又称为网络远程控制&#xff0c;它使用户能够通过互联网连接两台设备以解决问题。进行控制的电脑称为控制端&#xff0c;被控制的电脑则称为被控端。在远程访问过程中&#xff0c;控制端电脑掌握整个连接的操作。远程控制软件会捕获被控端电脑的操作&#xff0c;并在主…

JS数组怎么去重?| JavaScript中数组去重的14种方法

目录 一、利用for循环嵌套去重 二、利用splice() for循环嵌套&#xff08;ES5中最常用&#xff09; 三、利用indexOf() for循环去重 四、利用sort() for循环去重 五、利用includes() for循环去重&#xff08;ES7&#xff09; 六、利用Object键值对及其hasOwnProperty…

521源码网-免费网络教程-Cloudflare使用加速解析-优化大陆访问速度

Cloudfalre 加速解析是由 心有网络 向中国大陆用户提供的公共优化服务 接入服务节点: cf.13d7s.sit 接入使用方式类似于其它CDN的CNAME接入&#xff0c;可以为中国大陆用户访问Cloudflare网络节点大幅度加速&#xff0c;累计节点130 如何接入使用 Cloudflare 加速解析&#…

LabVIEW中进行步进电机的位置控制

在LabVIEW中进行步进电机的位置控制&#xff0c;通常涉及以下几个关键步骤&#xff1a;设置硬件、配置通信、编写控制算法和实施反馈控制。以下是一个详细的介绍。 硬件设置 步进电机&#xff1a;选择合适的步进电机&#xff0c;根据负载和应用需求选择适当的步数和转矩。 驱…

Kafka broker的新增和剔除(服役与退役)

说明&#xff1a;集群现有broker:node1,node2,node3三个,broker.id分别为0&#xff0c;1&#xff0c;2 已有两个topic&#xff1a;products、cities 1、退役&#xff08;Kafka集群中减少一个服务器broker2&#xff09; 退役后要保证剩下的服务器数量大于等于备份数&#xff0c…

SpringBoot+layui实现Excel导入操作

excel导入步骤 第三方插件引入插件 效果图 &#xff08;方法1&#xff09;代码实现&#xff08;方法1&#xff09;Html代码&#xff08; 公共&#xff09;下载导入模板 js实现 &#xff08;方法1&#xff09;上传文件实现 效果图&#xff08;方法2&#xff09;代码实现&#xf…

智慧医院物联网建设-统一管理物联网终端及应用

近年来&#xff0c;国家卫健委相继出台的政策和评估标准体系中&#xff0c;都涵盖了强化物联网建设的内容。物联网建设已成为智慧医院建设的核心议题之一。 作为医院高质量发展的关键驱动力&#xff0c;物联网的顶层设计与网络架构设计规划&#xff0c;既需要结合现代信息技术的…

AI炒股-批量爬取网易财经的要闻板块

工作任务和目标&#xff1a;批量爬取网易财经的要闻板块 在class"tab_body current"的div标签中&#xff1b; 标题和链接在&#xff1a;<a href"https://www.163.com/dy/article/J2UIO5DD051188EA.html">华为急需找到“松弛感”</a> 第一步&…

linux磁盘满了,如何查找大文件清除?

将整个Linux中文件按照文件大小排序&#xff0c;从大到小排序 只显示前100条数据 命令&#xff1a; find / -type f -exec du -h {} | sort -rh | head -n 100结果&#xff1a;

Llama改进之——分组查询注意力

引言 今天介绍LLAMA2模型引入的关于注意力的改进——分组查询注意力(Grouped-query attention,GQA)1。 Transformer中的多头注意力在解码阶段来说是一个性能瓶颈。多查询注意力2通过共享单个key和value头&#xff0c;同时不减少query头来提升性能。多查询注意力可能导致质量下…

联芸科技偏高的关联交易:业绩波动性明显,海康威视曾拥有一票否决

《港湾商业观察》施子夫 5月31日&#xff0c;上交所上市审核委员会将召开2024年第14次审议会议&#xff0c;届时将审议联芸科技&#xff08;杭州&#xff09;股份有限公司招股书&#xff08;以下简称&#xff0c;联芸科技&#xff09;的首发上会事项。 据悉&#xff0c;此次系…

php反序列化学习(3)

1、session 当session_start()被调用或者php.ini中session.auto_start为1时&#xff0c;php内部调用会话管理器&#xff0c;访问用户session被序列化后&#xff0c;存储到指定目录&#xff08;默认为/tmp&#xff09;。 漏洞产生&#xff1a;写入格式与读取格式不一致 处理器…

C# 代码配置的艺术

文章目录 1、代码配置的定义及其在软件工程中的作用2、C# 代码配置的基本概念和工具3、代码配置的实践步骤4、实现代码配置使用属性&#xff08;Properties&#xff09;使用配置文件&#xff08;Config Files&#xff09;使用依赖注入&#xff08;Dependency Injection&#xf…

模拟建造游戏:城市:天际线Cities: Skylines for Mac/win中文原生版

《城市&#xff1a;天际线》&#xff08;Cities: Skylines&#xff09;是一款由Colossal Order开发&#xff0c;Paradox Interactive发行的城市建设模拟游戏。这款游戏于2015年首次发布&#xff0c;迅速赢得了玩家和评论家的好评&#xff0c;并成为了备受欢迎的城市建设游戏之一…

Centos7.9环境下keepalived结合nginx实现负载均衡的高可用(亲测版)

目录 一、负载均衡高可用解释 二、安装 三、Nginx检查脚本创建 四、修改keepalived配置文件 一、负载均衡高可用解释 nginx 作为负载均衡器&#xff0c;所有请求都到了nginx&#xff0c;如果nginx服务器宕机后端web服务将无法提供服务&#xff0c;影响严重。这样nginx作为负…

使用 Django Model 构建强大的数据库模型

文章目录 创建一个简单的 Django Model迁移数据库使用 Django Shell 操作模型Django Admin结论 在 Django 中&#xff0c;Model 是构建数据库模型的基础。它允许开发人员定义数据的结构&#xff0c;并提供了方便的方式来与数据库进行交互。本文将介绍如何使用 Django Model 来创…

Vitis HLS 学习笔记--控制驱动与数据驱动混合编程

目录 1. 简介 2. 示例分析 2.1 代码分析 2.2 控制驱动TLP的关键特征 2.3 数据驱动TLP的关键特征 3. 总结 1. 简介 在 HLS 硬件加速领域&#xff0c;Vitis HLS 提供了强大的抽象并行编程模型。这些模型包括控制驱动和数据驱动的任务级并行性&#xff08;TLP&#xff09;&…

腾讯元宝APP横空出世,传统搜索面临巨大挑战

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 松松有个同事也叫&#xff1a;X元宝。我们公司旁边有个小吃街&#xff0c;就叫元宝街。每提到腾讯元宝&#xff0c;我就想起了我同事和这条街。 我今天看了腾讯混元大模型团队的发布会&#xff0c;他们发布了一款名…

存储 Bean 对象更加简单的方式

前置操作 如果是在 spring-config 中添加 bean 标签来注册内容&#xff0c;每个类都要弄一次就显得麻烦和臃肿了&#xff0c;对于 new 操作而言就没有什么优势了。因此 spring 就引入了注解操作来实现对 Bean 对象的存储。 配置扫描路径 想要将对象成功的存储到 Spring 中&…