c++如何判断二进制相同的对象? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
LuckyPocketWatch
V2EX    C++

c++如何判断二进制相同的对象?

  •  
  •   LuckyPocketWatch 2023-08-17 00:23:58 +08:00 3567 次点击
    这是一个创建于 790 天前的主题,其中的信息可能已经有所发展或是发生改变。

    假如有两个自定义类 class A 和 class B ,这两个自定义类的对象长度均为 32 字节,然后有如下代码

    A* ptr = new A(/*paratemerA*/); //先生成一个 class A 的对象为 objectA A* objectA = ptr; if(/*cOndition== true*/){ ptr->~A(); //销毁对象 class A ,但内存没有还给系统 ptr = new B(/*paratemerB*/);//在原来的内存上生成一个 class B 的对象为 objectB } if(ptr == objectA) ptr->doSomething(); 

    objectA 和 objectB 这两个类对象,长度均为 32 字节,这两个对象创建在相同的内存里(创建时间不一样),然后这段代码在运行时出现了一个巧合,生成的这两个对象二进制完全一样

    那这种情况下 C++时如何判断 ptr == objectA 这段代码的?

    36 条回复    2023-08-18 00:25:37 +08:00
    codehz
        1
    codehz  
       2023-08-17 00:41:17 +08:00 via iPhone
    需要考虑的问题是为什么你会有一个无效的指针需要比较?有这个无效的指针在,你问题是解决不完的…
    sunstar
        2
    sunstar  
       2023-08-17 00:50:11 +08:00 via iPhone
    ABA 问题?可以看看 brpc 的对象池
    polaa
        3
    polaa  
       2023-08-17 00:54:47 +08:00 via iPhone
    感觉你可能需要看 malloc 或者 calloc 相关堆实现
    包括 free chunk 之后插入 fast bin 或者 tcache 之类的
    好像涉及 uaf 或者 undefined behavior 漏洞吧
    堆相关知识忘光了 就这些吧
    Inn0Vat10n
        4
    Inn0Vat10n  
       2023-08-17 00:55:25 +08:00
    c++这里比的就是指针里存的地址,实际指向什么,指向的东西是不是合法的是不管的
    ashong
        5
    ashong  
       2023-08-17 01:18:57 +08:00 via iPhone
    调用了析构而没有删除,但新的 ptr 应该是指向新的 B 对象
    trn4
        6
    trn4  
       2023-08-17 01:52:32 +08:00
    你是可以要求编译器在指定的内存地址里面 new object 的,所以这不一定是巧合。https://isocpp.org/wiki/faq/dtors#placement-new

    ptr 能放 B 对象的指针说明这是一个父类指针指向子类的情况?那么比较 A*和 B*地址的时候应该会隐式转换成父类指针再比较。同类型指针指向同样地地址就是相同的。
    ryd994
        7
    ryd994  
       2023-08-17 01:57:45 +08:00 via Android
    你这个情况不正常。既然 A 的内存没有还给系统,那下面的 new 分配内存的时候就不会分配到 A 的内存。否则内存管理就有 bug 了。libc 这么多年,你随便就遇到内存管理 bug 的概率基本可以忽略。

    最简单的办法就是全局内存日志。在构造和析构函数里打日志用来分析

    一般没人乱飞野指针的。一般是配合引用计数或者 autoptr/smartptr 之类的使用。
    那引用计数也可以打日志
    ryd994
        8
    ryd994  
       2023-08-17 01:59:30 +08:00 via Android
    如果你只是想区分 A 对象和 B 对象,那让对象储存自己的名字即可,在构造函数里写入这个名字
    lany
        9
    lany  
       2023-08-17 03:10:41 +08:00
    objectA 指针里面存的 class A 的起始地址
    geelaw
        10
    geelaw  
       2023-08-17 03:12:09 +08:00
    假设你的代码是

    A *ptr = new A();
    A *objectA = ptr;
    ptr->~A();
    ptr = new (ptr) B(); // 注意这里需要用 placement new
    if (ptr == objectA) ptr->doSomething();

    并且 B 是 A 的派生类,并且假设 struct B : A { /* 没有额外的成员 */ };

    第一,我不知道 new (ptr) B() 是否是未定义行为,这需要翻阅标准。(适合于 A 的 storage 一定适合于 B 吗?)

    第二,接下来比较 ptr 和 objectA 绝对是 undefined behavior ,因为 objectA 最后一次赋值的时候指向的对象已经不存在了,所以 objectA 不是有效的指针这件事情和原来的 storage 上面现在有没有 B 类型的对象、B 是不是 A 的子类没有任何关系。

    让我来写一个正确的版本:

    char alignas(B) storage[sizeof(B)];
    A *ptr = new (storage) A();
    A *objectA = ptr;

    ptr->~A();
    ptr = new (ptr) B();

    objectA = std::launder(objectA); // 这一步非常重要

    if (objectA == ptr) ptr->doSomething(); /* if 的 true 分支会运行 */
    geelaw
        11
    geelaw  
       2023-08-17 03:16:01 +08:00
    @geelaw #10 原来的问题:C++ 如何判断 ptr == objectA 这段代码的?

    既然是未定义行为,编译器可以选择格式化你的硬盘,但大多数编译器并不会这样做。
    在我转写的第一段代码里,比较可能发生的有两种:

    1. 无事发生,直接进行数值的比较,因此 true 分支会运行。
    2. 编译器意识到
    (i) ptr 赋值获得的是新对象
    (ii) objectA 在 ptr 赋值之后就没有再赋值过
    于是意识到 objectA 和 ptr 在标准看来不可能同时有效地指向同一个对象,因此直接删除 if 的 true 分支,导致 true 分支不执行。
    dangyuluo
        12
    dangyuluo  
       2023-08-17 04:06:09 +08:00
    ```
    ptr->~A(); //销毁对象 class A ,但内存没有还给系统
    ptr = new B(/*paratemerB*/);//在原来的内存上生成一个 class B 的对象为 objectB
    ```
    并不能保证在原来的内存上生成一个 class B ,除非你用了 placement new

    而且这是个 UB
    dangyuluo
        13
    dangyuluo  
       2023-08-17 04:11:28 +08:00
    @gleelaw `operator==`在两个同类型指针上应该是 well defined 。`*ptr`才是 UB 。

    https://godbolt.org/z/7Kzs4x4Mn
    iceheart
        14
    iceheart  
       2023-08-17 06:10:46 +08:00 via Android
    就是地址比较,地址肯定不同,所以 if 条件肯定不成立
    lopssh
        15
    lopssh  
       2023-08-17 07:48:27 +08:00 via Android
    你这代码会在编译时就被咔擦掉吧。。。
    你可以将 A*的变量赋值为 B*,你这 A/B 是相互不兼容的吧?那不是动态类型语言的活儿么。
    lopssh
        16
    lopssh  
       2023-08-17 07:50:02 +08:00 via Android
    @sunstar ABA 是先 A 然后 B ,然后再 A ,丢失了有关于 B 的信息,这个例子就只是 AA 。
    lopssh
        17
    lopssh  
       2023-08-17 07:53:38 +08:00 via Android
    A* ptr = new A(/*paratemerA*/); //先生成一个 class A 的对象为 objectA
    A* objectA = ptr;

    if(/*cOndition== true*/){
    ptr->~A(); //销毁对象 class A ,但内存没有还给系统
    }

    if(ptr == objectA)
    ptr->doSomething();

    我看你还是用这个来举例吧。
    那句 ptr 的赋值毫不影响程序逻辑,如果 B 是 A 的子类的话,
    blinue
        18
    blinue  
       2023-08-17 08:41:57 +08:00
    你对指针有很大误解,学好基础啊
    hankai17
        19
    hankai17  
       2023-08-17 08:43:03 +08:00
    感觉是设计不合理 出现内存池条件竞争
    可以看一下 brpc 内存池管理
    mingl0280
        20
    mingl0280  
       2023-08-17 09:02:56 +08:00 via Android
    你不会觉得 ptr = new B 能编译过吧?
    mingl0280
        21
    mingl0280  
       2023-08-17 09:05:01 +08:00 via Android
    @dangyuluo 他原来给的这个代码,除非 ptr 是 void*而且赋值时用了强转,否则怎么想都无法通过编译吧?
    hxysnail
        22
    hxysnail  
       2023-08-17 09:13:43 +08:00
    如果你内存没有还给系统,那么 B 不可能用 A 原来的内存;
    如果你内存已经还给系统了,objectA 还指向被回收内存,那就是野指针问题,严重 BUG……
    sloknyyz
        23
    sloknyyz  
       2023-08-17 09:36:34 +08:00
    建议再好好学学指针相关的内容,你问的是很基础的东西了。判断 ptr==objectA,很简单,指针判断是否一样就是判断指针值是否一样,根本不会管指针指向了什么内容。你的代码里,按照你说的,两个对象内存相同,那指针的值就是一样的,因此最后的判断是 true 。还有个问题,就是 ptr = new B(), 这段代码是编译不过的,但如果想可以加个强制转换。
    antonius
        24
    antonius  
       2023-08-17 09:51:13 +08:00
    Q:如何判断 ptr == objectA ?
    A:就是指针指向地址的比较。
    ptr = new B 不一定不能通过编译,如果 B 是 A 的子类,是可以的。
    不过 objectA 是一个野指针,不算是一个好的编码习惯。
    araraloren
        25
    araraloren  
       2023-08-17 10:10:05 +08:00
    我们 c++的程序总能搞出不一样的花样,反观 rust 就不太行
    jujusama
        26
    jujusama  
       2023-08-17 11:00:56 +08:00
    还得是你 cpp 啊,写出这种东西。

    正经回答:指针判等仅比较指针变量的值,参考最小示例

    https://godbolt.org/z/axPfGezab
    aneostart173
        27
    aneostart173  
       2023-08-17 11:14:09 +08:00
    你这是重写了 allocator?
    geelaw
        28
    geelaw  
       2023-08-17 11:19:59 +08:00
    @dangyuluo #13 贴代码无意义,不过您说得对,我需要修正 #10 #11 的说法,因为

    根据 [basic.compound]/3 ,指向对象的指针的值“表示的地址”是该对象(若该对象不在其生命周期内,则是该对象曾经、将要)占有的存储的第一个字节。这表示 placement new 返回和传入的指针虽然可能指向了不同的对象,但是它们“表示的地址”相同。

    根据 [expr.eq]/3.2 ,两个同类型、指向对象的、不是指向末尾之后的指针相等,如果它们“表示的地址”相等。

    #10 里面建议的 std::launder 不必要,并且 #11 里面认为的未定义行为不成立。

    ---

    回到 @dangyuluo #12 我认为应该尽量按照楼主希望的意思自动更正他的代码,所以应该认为

    struct A { }; struct B : A { };

    并且 new B() 改写为 new (ptr) B()。

    ---

    @sloknyyz #23 @antonius #24 @jujusama #26 指针比较并不是比较数值,考虑

    alignas(C) std::byte storage[2 * sizeof(C)];
    C *a = new (storage) C() + 1;
    C *b = new (storage + sizeof(C)) C();
    /* 此时 a 和 b 必然表示相同的地址 */
    a == b ? 1 : 2; /* 结果可以是 2 见 [basic.compound]/3 的 note 和 [expr.eq]/3.1 */

    再比如

    void *p = std::malloc(1);
    std::free(p);
    p == p ? 1 : 2; /* 结果可以是 2 见 [basic.stc]/4 */
    geelaw
        29
    geelaw  
       2023-08-17 11:32:36 +08:00
    @geelaw #28 贴代码无意义是指贴编译之后的机器代码无意义,因为未定义行为包括任何行为,不能根据实现推断标准。
    yeziqing
        30
    yeziqing  
       2023-08-17 11:37:14 +08:00   1
    @antonius 看他给的代码只调用了 A 的析构函数,所以这时所占用的内存没被释放,也即 ojbectA 指向的仍是有效内存地址,应该还不算是里指针。
    geelaw
        31
    geelaw  
       2023-08-17 12:29:43 +08:00
    @geelaw 最后一段里面第一个例子是错误的,因为数组是 implicit-lifetime object ,于是在 a == b 执行的时候它的效果已经变成了 [expr.eq]/3.2 了。不过第二个例子 p == p 依然成立。
    cnbatch
        32
    cnbatch  
       2023-08-17 13:57:53 +08:00
    既然 OP 没说实际业务情况,那我接下来的思路就怎么方便怎么来

    首先,既然两个 class 确定都是 32 字节,那么可以当成 int32_t

    A *ptr_a = &class_a_var;
    B *ptr_b = &class_b_var;
    cnbatch
        33
    cnbatch  
       2023-08-17 14:08:41 +08:00
    (没写完就发了出去,重来)

    既然 OP 没说实际业务情况,那我接下来的思路就怎么方便怎么来

    首先,既然两个 class 确定都是 32 字节,那么可以当成 int32_t

    A *ptr_a = &class_a_var; // 或者是 new 出来的
    B *ptr_b = &class_b_var; // 或者是 new 出来的

    int32_t value_a = *((int32_t *)ptr_a);
    int32_t value_b = *((int32_t *)ptr_b);

    std::map<int32_t, std::time_t> stores;

    然后把 32 位字节当成 int32_t 存起来:

    if (stores.find(value_a) == stores.end())
    stores[value_a] = std::time(nullptr);

    if (stores.find(value_b) == stores.end())
    stores[value_b] = std::time(nullptr);

    需要对比时就:

    if (stores.find(value_a) != stores.end())
    //时间已经存起来了,自己想怎么用就怎么用;

    if (stores.find(value_b) != stores.end())
    //时间已经存起来了,自己想怎么用就怎么用;
    antonius
        34
    antonius  
       2023-08-17 15:41:14 +08:00
    @yeziqing 感谢指正,你说的没错,不算野指针。
    tool2d
        35
    tool2d  
       2023-08-17 16:57:24 +08:00
    以前有个手动内存管理机制的 flag ,叫 Tomb 墓碑。能确保 ptr 销毁后,短期内地址不会被重复利用。
    kirory
        36
    kirory  
       2023-08-18 00:25:37 +08:00
    RTTI, typeid
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     919 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 20:53 PVG 04:53 LAX 13:53 JFK 16:53
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86