拾遗与填坑《深度探索C++对象模型》3.2节

《深度探索C++对象模型》是一本好书,该书作者也是《C++ Primer》的作者,一位绝对的C++大师。诚然该书中也有多多少少的错误一直为人所诟病,但这仍然不妨碍称其为一本好书。本文志在填坑。

3章2节 Data Member的布局

背景介绍

访问区(access section)即是指private、public、protected下面的代码区域。当然在类中同一种访问区可以多次声明,视作多个访问区,如:

class Point3d {
public:
    // ...
private:
    float x;
private:
    float y;
private:
    float z;
};
// 该类有4个访问区

本节重点讲述的是在同一个访问区中声明的数据成员只需符合较晚出现的members在class object中有较高的地址这一条件即可。换言之,并不需要连续,再换言之,数据成员之间编译器可以穿插其他所需的东西,如虚表指针或边界调整的填充字节等。注意该条件是说同一访问区内。如果不同访问区呢?不同的编译器厂商会做不同的调整,有的会把同种的访问区合并,有的则不会。如果你想知道你的编译器针对同种的不同访问区做了什么,对数据成员布局做了何种调整,可以采用代码验证。好的,重点终于来了。

错码

为了验证上述的猜想,作者写了一段代码来检测两个成员的顺序。

template<class class_type, class data_type1, class data_type2>
char* access_order(data_type1 class_type::*mem1, data_type2 class_type::*mem2)
{
    return mem1 < mem2 ? "member 1 occurs first" : "member 2 occurs first";
}

access_order(&Point3d::z, &Point3d::y);

等等,先停一下,你会说:刚才那个Point3D的类,x,y,z这三个成员可都是private啊,他这个外部的函数可以直接访问吗?呵呵。你说的对,大师犯了这个错误。来吧我们把访问区都改成public

By the Way, 大师的代码测试的不是对象的内存布局,而是直接测试的类的内存布局。

&Pont3d::z这是直接对的成员而非其对象实例的成员来取地址,实际上它获得到的并不是地址,而是成员在类中偏移(offset)。

继续,或许大师当年的编译器是可以通过的。很不幸,在我的机器上报错了(g++ (GCC) 4.8.5 20150623):

invalid operands of types ‘float Point3D::*’ and ‘float Point3D::*’ to binary ‘operator<’
return mem1 < mem2 ? "member 1 occurs first" : "member 2 occurs first";

矛头直指这个比较操作。。

可能是目前的C++标准或者G++编译器自身不支持指针地址和类中offset的比较运算符。或者大师的代码本身就存在问题。真真假假,这点就不得而知了。

改之

最简单的测试方案就是:

干嘛非要测试类中数据成员的先后顺序,直接测试对象中数据成员的先后顺序不久行了嘛

此时Point3D的三个private都已改成public。。

    Point3D p;
    cout<< (&p.z < &p.y)<<endl;
    printf("%p\n", &p.z);
    printf("%p\n", &p.y);

输出:

0
0x7ffffd2d0458
0x7ffffd2d0454

其实到了这里,你该得出什么关于编译器如何调整数据成员布局顺序的结论,早就可以得出了。。不过那早已不是本文的重点,毕竟不同编译器有自己的实现自由,探究这个并无太多意义。。

再来直接打印一下类中成员mem1和mem2的值看看。

// 返回值也先改掉。
template<class class_type, class data_type1, class data_type2>
void  access(data_type1 class_type::*mem1, data_type2 class_type::*mem2)
{
    printf("%p\n", mem1);
    printf("%p\n", mem2);
}

输出结果:

0x8
0x4

这两个如此简洁的地址就是类中offset了(可以看出和指针确实差别挺大,短了好多)。
不过我感觉大师代码中那个模板用的当真漂亮,所以还是想着让大师的思想继续发光发热下去,用来测试你的编译器咋处理的。遂改之。咋办?类型转换呗:

// 方案一 报错
return (char*)mem1 < (char*)mem2 ? "member 1 occurs first" : "member 2 occurs first"; 
// 方案二 依旧报错
return static_cast<char*>(mem1) < static_cast<char*>mem2 ? "member 1 occurs first" : "member 2 occurs first";  
// 方案三 还TM报错
return reinterpret_cast<char *>(mem1) < reinterpret_cast<char *>(mem2) ? "member 1 occurs first" : "member 2 occurs first";  

。。等会。看来天亡大师(的代码),强制类型转换是行不通了。。哈哈,如果你看到这里,你还是非要做一个比较操作(不通过对象),让控制台直接告诉你谁前谁后。。那么来吧,带大家一起继续脱裤子放屁。

union Cmp{
    float Point3D::* mem;
    long offset;
};
template<class class_type, class data_type1, class data_type2>
const char * access_order(data_type1 class_type::*mem1, data_type2 class_type::*mem2)
{
    Cmp cmp1 = {mem1}; // 初始化cmp1的第一个成员mem
    Cmp cmp2 = {mem2}; // 初始化cmp2的第一个成员mem

    return cmp1.offset< cmp2.offset?"member 1 occurs first" : "member 2 occurs first";
}

终于看到了member 2 occurs first,,代码调通了。不过丑的一笔。


后记:

代码段中类的数据成员的顺序和实例化后对象中数据成员的顺序是否具有一致性呢?这点我不确定,或许应该是吧。实例化操作应该就是栈中(或堆)模塑了代码段中的类模型,然后进行了初始化。而编译器自己添加的那些东西,那些调整工作在代码段中的类模型中就已完成了。我是这样理解的,希望大家指教。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,816评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,729评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,300评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,780评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,890评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,084评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,151评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,912评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,355评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,666评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,809评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,504评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,150评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,121评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,628评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,724评论 2 351

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,638评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,602评论 18 399
  • 3章3节 Data Member的存取 背景介绍 本节篇幅较短核心内容讲的就是,如下代码的存取效率问题: 效率是否...
    果冻虾仁阅读 637评论 0 0
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,867评论 25 707
  • 一、一月整体分析 一月份三个核心目标完成情况: 1、工作:完成90%,最后10%没完成的原因,是自己不能改变的,也...
    Hello禾页阅读 282评论 0 0