news 2026/4/16 11:50:43

C语言对话-11.根源

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言对话-11.根源

徐波 翻译

这个方塔比在录像中看到的还要大一些。

我站在人造悬崖的边缘,它处于挖掘现场的的边缘,旁边是一层稍呈褐色的冰。方塔朝着我的那一面看上去象是纯水晶,表面散发着一种光辉,徐徐向上,在大约距我脑袋十二米的地方是锥形的塔尖,仿佛是指向那儿的指针。再上去一点,淡灰色的塔顶内表面罩在我们头顶,覆盖了整个区域。

远处隐隐传来取暖器的嗡嗡声,尽管衣服穿得并不少,我仍然感到一丝寒意。我们呼吸的气息静静地停滞在我们面前的空气中。气温还算能够忍受,但仍低于零度很多,冰是不会融化的。

在我的下面,石塔稍显宽阔,延伸二十几米后到达它与顶部联接处,只有部分已被挖掘能够看到,那个更宽阔的建筑,我知道它是一座建筑的屋顶,几乎完全被冰所覆盖。仅根据我下面所看到的屋顶,无法用视觉来判断这个建筑的尺寸,但我根据测声认为它钻得相当深,很可能直达冰层的下面。

“入口在远处那边的下面”,我的同伴说,“是个大家伙吧?” 我的思绪又回到了当前,点了点头,微微冲她一笑。我们向下走到挖掘现场的地面上,绕着放在那儿的机器以及两三个一组工作的人们缓步而行。

石塔底部的入口是唯一属于人造的东西。它的边缘很粗糙,不久前还是一堵墙,很明显不是这个古代建筑原先设计的一部分。

入口有点暗,但在不远处有临时的照明设备。我当时肯定有些犹豫,因为我同件的手抓住我手臂,试图打消我的疑虑。“好了,”我笑着回头对珍妮说,“我并不是对一切都想刨根问底。”

“是的,哈!那是我们的管理员。”

“那么,我们午餐时再见。”我对安娜说,然后挂上了电话,她是我的新任女友。我们已经外出够长时间了,对于她是Guru的女儿,我感到很惬意,而在我们刚开始约会时我并不知道这一点。

“嗨,伙计!”鲍勃的叫声打断了我快乐的瑕想。“我想我告诉过你不要破坏结构。”

我回过头,用最耐心的声音回答:“鲍勃,你应该记得,我最后一次破坏结构是因为有些...是不是该这么说,你的模块中有不够理想的代码。”嗯,象往常一样,他又在呷他的咖啡。

“哈哈,这次是你的问题,伙计!”鲍勃啧啧有声,“经过的你修改,我的代码总是崩溃。”

我叹了口气,不想与他争执,“好了,鲍勃,告诉我问题出在哪里,我来看看。”

“这次肯定是你的缘故,”鲍勃笑着说,表情却颇为不快,“搞清楚这儿谁的经验更丰富,伙计。不要以为从古怪的Guru那儿吸点营养就能指手划脚。再说,对于我,她的权威实在有限。”他写下问题文件的名称后就走了。我只能听天由命,开始检查这个模块。

一小时后,我还是没理出个头绪来。“噢!这对我来说太奇怪了,”我喃喃自语。这看上去象是我自己代码的问题——鲍勃的代码在原先的类上工作良好,但在我修改过的类上工作却有问题。

看上去类层次结构相当简单:

class parent

{

public:

virtual void f();

// etc...

};

class child : public virtual parent

{

public:

void f();

};

我所做的修改之一就是把child虚拟地继承于parent,使它在这个类层次结构的任何地方都能使用。我尽了最大努力,但看上去我别无选择——我打算深入挖掘鲍勃的代码了。“又要搞破坏了,亲爱的朋友”,我嘀咕着,设置了一个断点,准备单步跟踪这个函数。经过半小时的严格调试,我从一团杂乱中找到了问题的根源:

void parentPtrPtr(parent **b)

{

(*b)->f();

}

void childPtrPtr(child **d)

{

parentPtrPtr(reinterpret_cast<parent **>(d));

}

对于最后一个函数,我自有想法。我知道要谨慎对待cast,特别是reinterpret_cast。我去掉reinterpret_cast,不出所料,编译器卡在这行代码上,抱怨paraent**和child**是不相关的类型。

“你的怀疑是对的,我的学徒工。”当Guru柔和的声音在我身后响起时,我记得只有一次没被她吓一跳。这次也是如此,但没有太吃惊。“指向child的指针的指针,”她接着说:“不能被隐式转换为指向base的指针的指针。

我向后踢了踢腿,伸了伸懒腰,“我也这样想。鲍勃所做的一切好象就是为了避免编译器报错。我不理解为什么它能在原先的类上工作,但现在却会导致崩溃。唉,我甚至不明白为什么编译器不能做隐式类型转换。”

“你现在所体验的崩溃正是它不允许这么做的十足理由,”Guru理了理耳后一络银色的头发,“你的派生类虚拟地继承于基类。”我象一只在车灯前不知所措的鹿一样紧紧盯着她,让她知道我没搞明白。“考虑一种简单继承的情况,”她说,拿起一支笔,开始在我的白色书写板上写起来:

class parent { /* 不管怎样都行,但至少要有一个虚拟函数*/ };

class child : public parent { /* 随便怎样都行 */ };

“虚拟函数的一种典型实现,”她接着说,“在内存布局中,编译器首先会给类附加一个指向虚拟函数表(vptr)的指针,然后是parent类的数据,再接下来是child类的数据。”

parent::vptr

parent data

child data

“等一下,”我打断说,“你不是经常告诉我虚拟函数表并非标准所必需,而只是实现方案的细枝未节?”

“是这样,我的的孩子。神圣的标准并没有规定必须要有虚拟函数表,事实上,它甚至没提到它。然而,庄严的标准委员会的成员们的目标之一就是规范化现有编译器的实际情况。允许child** 隐式转换为parent**将在许多现有的编译器上造成问题。其中一个特别的问题就是我接下来要说的,如果你允许我说下去的话。”她严肃地看着我,我没敢说什么。

“这种布局方式意味着this所指向的地址可以既是基类又是派生类。使用我们所假设的编译器,this始终指向vptr。派生类的成员函数可以简单地通过this取得其数据成员的地址,只要加上一个包括基类大小和vptr大小的偏移量即可。

“然而,当你在这个混合体里再加上虚拟继承时,情况就变得更加复杂,考虑一下:”

class parent { /* whatever */ };

class child1 : public virtual parent { /* whatever */ };

class child2 : public virtual parent { /* whatever */ };

class multi : public child1, public child2 { /* whatever */ };

“显然,由于上面parent类的数据紧随child类的数据之后,编译器无法对中间类child1和child2采取和child一样的布局形式,因为虚拟继承规定只能出现基类数据的一份拷贝。解决方案——确切地说,解决方案之一就是在每个子对象的开始处附加一个vptr,如下所示:”

parent::vptr

parent data

child1::vptr

child1 data

child2::vptr

child2 data

multi::vptr

multi data

“这样,当编译器触发各种成员函数时,它可以动态地调整隐式的this参数,从而使成员函数能够正确地访问成员数据。”

“哦!我明白你的意思了,”我说,“当你有一个multi对象时,指向它的指针的值有时会稍有不同,这取决于它是指向parent子对象还是child1子对象。这也就解释了为什么中间类必须使用virtual关键字——我以前一直认为这是个错误,而multi应该是使用virtual的类。所以,不管怎样,让我们回到原来的那个问题。这就是说……嗯……不,我还是不太明白,这跟指向指针的指针又有什么关系呢?”

她慢条斯理地说:“让我们给这些值分配任意的内存地址,假定每个类恰好包含一个整型数,这样整型数和指针的大小都是4个字节,不存在填充和对齐问题。让我们再加上一些代码,举几个multi类的实例:”

void f()

{

multi m;

multi *pm = &m;

multi **ppm = &pm;

//...

Address

Description

Value

0x1000

parent::vptr

?

0x1004

parent data

?

0x1008

child1::vptr

?

0x100c

child1 data

?

0x1010

child2::vptr

?

0x1014

child2 data

?

0x1018

multi vptr

?

0x101c

multi data

?

0x1020

Pm

0x1000

0x1024

Ppm

0x1020

“编译器将multi对象地址的第一个字节作为multi指针所指向的地方,也就是0x1000。现在假定你往这个函数里再添加一些变量:”

//...

child1 * pc1 = &m;

child1 ** ppc1 = &pc1;

//...

“编译器简单地取m的地址,将它调整为正确的子对象(在这里,也就是child1子对象的起始处),把它赋值给pcl。ppcl只是简单地包含pc1的地址:”

Address

Description

Value

0x1028

Pc1

0x1008

0x102c

ppc1

0x1028

//...

“但现在假定你想把ppm赋值给ppcl:”

//…

ppc1 = ppm; //该怎么处理呢?

//…

“假定编译器允许你把装在ppm内的值直接赋值给ppc1,ppc1的值就会被设为0x1020,也就是pm的地址,pm指向的是multi的基地址0x1000。当你对ppc1进行“提领”(dereference)操作时,你希望得到一个指向child1指针,但事实上你得到的却是指向parent的指针(或指向multi的指针,它们在我们假定的编译器里有相同的基地址。)”

“我明白了,”我惊叹道,“但等一下,为什么它在原来的类里没问题呢?”

“在非虚拟的继承里,这个特定的编译器把相同的基地址赋值给child和parent子对象。本质上,这两个地址的比特模式(bit-pattern)恰好相同,所以reinterpret_cast碰巧能正确工作。”

“太好了,”我说,“我明白这个道理了。现在我该怎么解决这个问题呢?”

“记住这句话‘每个问题都可以通过附加一个间接层来解决’。现在这种情况下,我的孩子,我们的间接层已经太多了。”

“太多的间接层,”我陷入沉思,突然,灵光一闪,“去掉一个间接层!”我迅速地在白色写字板写了一些代码。

void

childPtrPtr(child **d)

{

parent *pp = *d;

parentPtrPtr(&pp);

}

“很好,我的孩子。当你将child*赋值给parent*时,就会对this参数进行调整。现在,parentPtrPtr函数得到的是正确的指向parent的指针的指针。”

“而且很及时,”我说,看见安娜正在走过来,“我有个饭局。”Guru优雅地起身告辞。这时,鲍勃晃了过来,他正去取新鲜的咖啡。他向安娜打了打招呼:

“嗨!小甜甜,什么风把你吹来了?一块儿吃午饭吧。”

“下次吧,爸。我约好了跟男友一块吃午饭。”安娜指指我。

我惊骇地盯着鲍勃,他也以同样的眼神盯着我。我们不约而同地转向安娜,脱口而出:“他是你的什么?”

“他是我们的什么?”我眨了眨眼睛。

“我们的管理员,”珍妮向一个过来加入我们的警官挥挥手,“我们必须向他汇报。”

“你好,卡露莎博士,”警官对珍妮说,“这位是——?”珍妮点点头,作了一下介绍。“你们的衣服在里面,”警官说,他把我们带到里面。“按规定,你们在下面必须始终穿着它,头盔要戴好,即使是在是加过压的房间里。”

在方塔里面,这个有照明装置的地方原来是个中等大小的房间,墙上挂满了衣服,地面中央有个巨大的洞,周围被带网孔的栅栏所包围。“把你们的手放在那儿进行签到,”警官告诉我们,“接着去取衣服并检查一下。”当我们按其指示完成后,从洞里升起一个平台,停在与地面差不多高的地方。我们通过栅栏的门走进去,接着这个升降机就开始下降。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:08:33

基于深度学习YOLOv11的食物检测系统(YOLOv11+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)

一、项目介绍 本文介绍了一个基于深度学习YOLOv11算法的食物检测系统&#xff0c;能够准确识别30类常见食物及饮品。系统整合了完整的YOLO数据集、用户友好的UI界面&#xff08;含登录注册功能&#xff09;以及Python项目源码与预训练模型。该模型在包含14,661张图像的数据集上…

作者头像 李华
网站建设 2026/4/16 10:42:33

《唐朝诡事录之西行》——独孤羊放妻春条书

前年暑期&#xff0c;电视剧《唐朝诡事录之西行》播出&#xff0c;其中“仵作之死”单元令我印象深刻&#xff0c;尤其是独孤羊写给妻子春条的那封休书。基于这份触动&#xff0c;我使用 Unity3D 引擎制作了一个小项目&#xff0c;通过 TextMeshPro 实现文本横竖排显示&#xf…

作者头像 李华
网站建设 2026/4/1 20:08:00

一文讲清楚Java中的抽象类、接口和内部类三大特性

目录 第一章 抽象类 1.1 概述 1.1.1 抽象类引入 1.2 abstract使用格式 1.2.1 抽象方法 1.2.2 抽象类 1.2.3 抽象类的使用 1.3 抽象类的特征 1.4 抽象类的细节 1.5 抽象类存在的意义 第二章 接口 2.1 概述 2.2 定义格式 2.3 接口成分的特点 2.3.1.抽象方法 2.3.…

作者头像 李华
网站建设 2026/4/16 10:41:38

基于深度学习的可视化植物病害检测系统(YOLOv8+YOLO数据集+UI界面+Python项目+模型)

一、项目介绍 摘要 本项目开发了一套基于YOLOv8目标检测算法的可视化植物病害智能检测系统&#xff0c;专门用于识别和分类30种不同的植物叶片病害。系统训练数据集包含2009张训练图像和246张验证图像&#xff0c;涵盖了苹果、蓝莓、樱桃、玉米、桃子、土豆、大豆、草莓、番茄…

作者头像 李华