假如有两个自定义类 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 这段代码的?
1
codehz 2023-08-17 00:41:17 +08:00 via iPhone
需要考虑的问题是为什么你会有一个无效的指针需要比较?有这个无效的指针在,你问题是解决不完的…
|
2
sunstar 2023-08-17 00:50:11 +08:00 via iPhone
ABA 问题?可以看看 brpc 的对象池
|
3
polaa 2023-08-17 00:54:47 +08:00 via iPhone
感觉你可能需要看 malloc 或者 calloc 相关堆实现
包括 free chunk 之后插入 fast bin 或者 tcache 之类的 好像涉及 uaf 或者 undefined behavior 漏洞吧 堆相关知识忘光了 就这些吧 |
4
Inn0Vat10n 2023-08-17 00:55:25 +08:00
c++这里比的就是指针里存的地址,实际指向什么,指向的东西是不是合法的是不管的
|
5
ashong 2023-08-17 01:18:57 +08:00 via iPhone
调用了析构而没有删除,但新的 ptr 应该是指向新的 B 对象
|
6
xiadong1994 2023-08-17 01:52:32 +08:00
你是可以要求编译器在指定的内存地址里面 new object 的,所以这不一定是巧合。https://isocpp.org/wiki/faq/dtors#placement-new
ptr 能放 B 对象的指针说明这是一个父类指针指向子类的情况?那么比较 A*和 B*地址的时候应该会隐式转换成父类指针再比较。同类型指针指向同样地地址就是相同的。 |
7
ryd994 2023-08-17 01:57:45 +08:00 via Android
你这个情况不正常。既然 A 的内存没有还给系统,那下面的 new 分配内存的时候就不会分配到 A 的内存。否则内存管理就有 bug 了。libc 这么多年,你随便就遇到内存管理 bug 的概率基本可以忽略。
最简单的办法就是全局内存日志。在构造和析构函数里打日志用来分析 一般没人乱飞野指针的。一般是配合引用计数或者 autoptr/smartptr 之类的使用。 那引用计数也可以打日志 |
8
ryd994 2023-08-17 01:59:30 +08:00 via Android
如果你只是想区分 A 对象和 B 对象,那让对象储存自己的名字即可,在构造函数里写入这个名字
|
9
lany 2023-08-17 03:10:41 +08:00
objectA 指针里面存的 class A 的起始地址
|
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 分支会运行 */ |
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 分支不执行。 |
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 |
13
dangyuluo 2023-08-17 04:11:28 +08:00
|
14
iceheart 2023-08-17 06:10:46 +08:00 via Android
就是地址比较,地址肯定不同,所以 if 条件肯定不成立
|
15
lopssh 2023-08-17 07:48:27 +08:00 via Android
你这代码会在编译时就被咔擦掉吧。。。
你可以将 A*的变量赋值为 B*,你这 A/B 是相互不兼容的吧?那不是动态类型语言的活儿么。 |
16
lopssh 2023-08-17 07:50:02 +08:00 via Android
@sunstar ABA 是先 A 然后 B ,然后再 A ,丢失了有关于 B 的信息,这个例子就只是 AA 。
|
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 的子类的话, |
18
blinue 2023-08-17 08:41:57 +08:00
你对指针有很大误解,学好基础啊
|
19
hankai17 2023-08-17 08:43:03 +08:00
感觉是设计不合理 出现内存池条件竞争
可以看一下 brpc 内存池管理 |
20
mingl0280 2023-08-17 09:02:56 +08:00 via Android
你不会觉得 ptr = new B 能编译过吧?
|
21
mingl0280 2023-08-17 09:05:01 +08:00 via Android
@dangyuluo 他原来给的这个代码,除非 ptr 是 void*而且赋值时用了强转,否则怎么想都无法通过编译吧?
|
22
hxysnail 2023-08-17 09:13:43 +08:00
如果你内存没有还给系统,那么 B 不可能用 A 原来的内存;
如果你内存已经还给系统了,objectA 还指向被回收内存,那就是野指针问题,严重 BUG…… |
23
sloknyyz 2023-08-17 09:36:34 +08:00
建议再好好学学指针相关的内容,你问的是很基础的东西了。判断 ptr==objectA,很简单,指针判断是否一样就是判断指针值是否一样,根本不会管指针指向了什么内容。你的代码里,按照你说的,两个对象内存相同,那指针的值就是一样的,因此最后的判断是 true 。还有个问题,就是 ptr = new B(), 这段代码是编译不过的,但如果想可以加个强制转换。
|
24
antonius 2023-08-17 09:51:13 +08:00
Q:如何判断 ptr == objectA ?
A:就是指针指向地址的比较。 ptr = new B 不一定不能通过编译,如果 B 是 A 的子类,是可以的。 不过 objectA 是一个野指针,不算是一个好的编码习惯。 |
25
araraloren 2023-08-17 10:10:05 +08:00
我们 c++的程序总能搞出不一样的花样,反观 rust 就不太行
|
26
jujusama 2023-08-17 11:00:56 +08:00
|
27
aneostart173 2023-08-17 11:14:09 +08:00
你这是重写了 allocator?
|
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 */ |
30
yeziqing 2023-08-17 11:37:14 +08:00 1
@antonius 看他给的代码只调用了 A 的析构函数,所以这时所占用的内存没被释放,也即 ojbectA 指向的仍是有效内存地址,应该还不算是里指针。
|
31
geelaw 2023-08-17 12:29:43 +08:00
@geelaw 最后一段里面第一个例子是错误的,因为数组是 implicit-lifetime object ,于是在 a == b 执行的时候它的效果已经变成了 [expr.eq]/3.2 了。不过第二个例子 p == p 依然成立。
|
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; |
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()) //时间已经存起来了,自己想怎么用就怎么用; |
35
tool2d 2023-08-17 16:57:24 +08:00
以前有个手动内存管理机制的 flag ,叫 Tomb 墓碑。能确保 ptr 销毁后,短期内地址不会被重复利用。
|
36
kirory 2023-08-18 00:25:37 +08:00
RTTI, typeid
|