CONTAINING_RECORD宏原理与使用详解

2023-11-09

先不急着说CONTAINING_RECORD宏,我们从最浅显的代码开始讲解。

一、0指针的妙用

0指针,即nullptr、NULL,空指针,是不是很常见,一遇到它往往就是segment fault。代码,如下:

struct Test
{
    int a;
    float b;
};

Test* pTest = nullptr;
int a_value = pTest->a; // segment fault
int b_value = pTest->b; // segment fault
pTest->a = 1;           // segment fault
pTest->b = 1.0;         // segment fault

上述想必没有疑问吧。空指针难道就没有用了?不,下面看一个巧妙的应用。

typedef unsigned long long quint64;
Test* pTest = nullptr;
quint64 offset_a = (quint64)(&(pTest->a));  // offset_a==0
quint64 offset_b = (quint64)(&(pTest->b));  // offset_b==4

offset_a为0,offset_b为4,如果你觉得很惊喜,请继续看。

之所以pTest->a,pTest->b没有报错,是因为前面添加了取地址符&,相当于我们告诉编译器,并不是真的要取a、b的值,我们只是取a、b的地址,然鹅Test对象的地址pTest为0,根据C++对象内存布局,如下图,可知当Test对象地址为0时,实际上取到的a、b地址,也就是a、b变量相对于整个对象首地址的偏移量。

在这里插入图片描述

小结:

利用某结构的空指针,对该结构成员取地址,可以得到该成员的偏移量。

换言之,我们很容易利用成员地址反推出整个对象的地址。

二、CONTAINING_RECORD宏

CONTAINING_RECORD宏定义位于winnt.h中,如下:

//
// Calculate the address of the base of the structure given its type, and an
// address of a field within the structure.
//

#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                  (PCHAR)(address) - \
                                                  (ULONG_PTR)(&((type *)0)->field)))

该宏的功能,是根据某个结构体中成员变量的地址,计算出结构体地址。

  • address,成员变量地址
  • type,结构体类型
  • field,成员变量名

该宏定义的原理,就是上一章中介绍的使用0指针获取成员偏移,然后再使用成员变量地址-成员偏移,就得到了结构体地址。

该宏定义的使用,如下:

struct School
{
    int level;
    float cost;
};

struct Student
{
    int age;
    School school;
};

School sch;
sch.level = 1;
sch.cost = 5000;

Student zhangsan;
zhangsan.age = 10;
zhangsan.school = sch;

Student* pStu = CONTAINING_RECORD(&zhangsan.school, Student, school);
qDebug() << &zhangsan;
qDebug() << pStu;

运行结果:

在这里插入图片描述

pStu与&zhangsan值相等。

貌似看起来,好像这么操作一遍,没什么用处。但是实际上,在某些情况下,还是非常有用的一个宏。

三、使用C++封装使用CONTAINING_RECORD宏

代码如下:

#include <QCoreApplication>
#include <QDebug>

//
// Calculate the address of the base of the structure given its type, and an
// address of a field within the structure.
//

#define CONTAINING_RECORD(address, type, field) ((type *)( \
                                                 (qint8*)(address) - \
                                                 (quint64)(&((type *)0)->field)))
// 轮子
class Wheel
{
public:
    Wheel(int count, int color)
        : count(count),
          color(color)
    {}

private:
    int count; // 数量
    int color; // 颜色
};

// 汽车
class Car
{
public:
    Car(int seat, Wheel wheel)
        : seat(seat),
          wheel(wheel)
    {}

    Wheel* getWheel()
    {
        return &wheel;
    }

    // 将轮子转换为对应的汽车
    static Car *wheelToCar(Wheel *pWheel)
    {
        return CONTAINING_RECORD(pWheel, Car, wheel);
    }

private:
    int seat; // 座位数量
    Wheel wheel; // 轮子
};

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Wheel wheel(4, 1); // 4个轮子,1种颜色
    Car myCar(5, wheel); // 5个座位,1套轮子

    Car* pCar = Car::wheelToCar(myCar.getWheel());
    qDebug() << &myCar;
    qDebug() << pCar;

    return a.exec();
}

运行结果:

在这里插入图片描述

用途:

当我们需要将一个class中,某个成员对象(struct或class),取出来,传递给其他api,其他api将这个成员对象作为参数进行一些处理,处理完后,还会将该对象传递回给我们,这时候,我们希望能够通过此对象获取到所在的整个对象,那么我们使用这个C++封装的宏,实现起来会很方便。

例如:

win下的IOCP异步IO中,会把OVERLAPPED结构传给api,处理完,会传回OVERLAPPED结构体。

以及linux下libaio异步IO中,也会iocb结构传给api,处理完,会传回iocb结构体。



若对你有帮助,欢迎点赞、收藏、评论,你的支持就是我的最大动力!!!

同时,阿超为大家准备了丰富的学习资料,欢迎关注公众号“超哥学编程”,即可领取。

本文涉及工程代码,公众号回复:19ContainingRecord,即可下载。

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CONTAINING_RECORD宏原理与使用详解 的相关文章

  • 【Python】字典dict 基础用法

    参考资料 Python字典 Dictionary 操作全解 创建 读取 修改 添加 删除 有序字典 浅复制 排序 字典是 键 值 对的无序可变序列 字典中的每个元素可以分为两部分 键 和 值 定义字典时 每个元素的 键 和 值 用冒号分隔
  • 朴素贝叶斯分类器

    贝叶斯分类器 优点 数据较少任然有效 可以处理多类别的问题 缺点 输入数据准备方式比价敏感 数据类型 标称型数据 朴素贝叶斯决策理论 假设有两类数据 A 和 B 假设有两类数据 A和B 假设有两类数据 A和B 平面直角坐标

随机推荐

  • springboot开发环境生产环境配置文件切换

    一 单个文件切换环境里 在application yml配置文件中添加如下信息 server port 8081 spring profiles active prod 激活prod生产环境 server port 8083 spring
  • “物联网+区块链”技术给农业发展带来新机遇

    运用 物联网 区块链 技术 从鸡苗供应 养殖 再到屠宰 流通等环节 每一只肉鸡的信息数据都被如实记录在 身份证 上 且不可篡改 经区块链加密的多宝鱼生长数据 通过 绿色履历 呈现在消费者面前 从土地承包到播种 加工 稻米的相关信息全部在区块
  • 【机器学习实战】5、Logistic 回归

    文章目录 5 1 基于Logistic回归和Sigmoid函数的分类 5 2 基于最优化方法的最佳回归系数确定 5 2 1 梯度上升法 5 3 python实战 5 3 1 查看数据集分布情况 5 3 2 训练 5 3 3 绘制决策边界 5
  • OpenCV相机标定全过程

    一 OpenCV标定的几个常用函数 findChessboardCorners 棋盘格角点检测 bool findChessboardCorners InputArray image Size patternSize OutputArray
  • Go语言在机器学习中有未来吗?

    Go 是一种开源编程语言 最初由 Google 设计 用于优化系统级服务的构建和使用 在大型代码库上轻松工作 以及利用多核联网机器 Go 于 2009 年推出 作为一种静态类型和编译型编程语言 深受 C 语言的影响 注重简单性 安全性和并发
  • ros 源码安装

    版本lunar 系统版本debian 9 8 参考 http wiki ros org lunar Installation Source 1 Installing bootstrap dependencies sudo apt get i
  • Flutter 实现安卓原生系统级悬浮窗

    Flutter实现安卓原生系统级悬浮窗 原创 As Kai 博客地址 https blog csdn net qq 42362997 如果以下内容对您有帮助 点赞点赞点赞 最近碰到了一个需求 使用Flutter实现悬浮窗效果 想来想去只能使
  • 关于 ioctl 的 FIONREAD 参数

    ioctl 是用来设置硬件控制寄存器 或者读取硬件状态寄存器的数值之类的 而read write 是把数据丢入缓冲区 硬件的驱动从缓冲区读取数据一个个发送或者把接收的数据送入缓冲区 ioctl keyFd FIONREAD b 得到缓冲区里
  • cmake message显示

    cmake message 在output中显示 PS C mywork mycpp helloworld build gt cmake Configuring done Generating done Build files have b
  • 雅可比算法求矩阵特征值C语言源代码,雅可比(Jacobi)计算特征值和特征向量

    雅可比迭代法法 在图形图像中不少地方用到求矩阵的特征值和特征向量 好比主成分分析 OBB包围盒等 编程时通常都是用数值分析的方法来计算 这里介绍一下雅可比迭代法求解特征值和特征向量 雅可比迭代法的原理 网上资料不少 详细可见参考资料1 这里
  • 【编写UI自动化测试集】Appium+Python+Unittest+HTMLRunner​

    简介 获取AppPackage和AppActivity 定位UI控件的工具 脚本结构 PageObject分层管理 HTMLTestRunner生成测试报告 启动appium server服务 以python文件模式执行脚本生成测试报告 下
  • Linux中桥接模式配置IP

    前言 本文主要介绍如何使用桥接模式配置IP 在使用虚拟机进行通信时 经常需要面临三种模式选择 不同模式之间配置不同 系统环境 CentOS 7 两种模式区别 NAT 虚拟机从属于主机 访问外部网络必须通过主机访问 虚拟机的IP只有主机才能识
  • 用DETR进行目标检测的predicat文件

    文章目录 前言 二 使用步骤 1 更改官方detr中308行的类别数 2 根目录加入检测文件 总结 前言 由于最近研究DETR目标检测 但是发现官方给的代码缺少了predict文件 于是自己写了一个 但是写代码的过程中也想到了一些问题 比如
  • QT QPushButton点击事件的实现

    最近需要对按钮实现效果 当鼠标按下时按钮有效果1 然后鼠标松开时按钮有效果2 分析 这里没有现成的信号可以使用 需要重载mousePressEvent 及 mouseReleaseEvent 事件函数 在mouseReleaseEvent
  • 技术笔记

    https docs qq com doc DUVBFRUNvUW1SUXB5
  • js模仿f11全屏_js触发f11全屏

    document on keydown function e var e event window event arguments callee caller arguments 0 if e e keyCode 122 捕捉F11键盘动作
  • 【CSS】css样式控制div水平垂直居中的六种方法

    1 绝对定位方法 不确定当前div的宽度和高度 采用 transform translate 50 50 当前div的父级添加相对定位 position relative 如图所示 代码如下 div background red posit
  • grep、sed、awk、perl等对正则表达式的支持的差别

    以perl的正则为基准 不同的用法以粉红色标出 因为spacemacs支持vim正则表达式操作 而且能够实时显示匹配内容和替换结果 可以作为perl脚本的正则表达式debug手段 转载来自 grep sed awk perl等对正则表达式的
  • Qt:信号和槽总结(1,C++11下的信号和槽 2,第五个参数 3,阻塞 )

    Qt 信号和槽总结 1 C 11下的信号和槽 2 第五个参数 3 阻塞 信号和槽是Qt特有的一种通讯方法 具有以下特点 信号与槽的连接比较灵活 可以一对一 一对多或者是多对一 信号与槽的绑定与解除也十分的灵活 使用connect以及disc
  • CONTAINING_RECORD宏原理与使用详解

    先不急着说CONTAINING RECORD宏 我们从最浅显的代码开始讲解 一 0指针的妙用 0指针 即nullptr NULL 空指针 是不是很常见 一遇到它往往就是segment fault 代码 如下 struct Test int