求教个神奇的 C++ 打印问题

46 天前
 Betsy

代码

#include <iostream>
#include <vector>
#include <cstdint>
#include <optional>

using GroupId = std::uint64_t;
using ReducedGroupId = GroupId;

struct Memo {
    std::optional<ReducedGroupId> GetReduceGroupId(const GroupId& group_id) {
        // omit
        return std::make_optional<ReducedGroupId>(group_id);
    }
};

int main(int argc, char* argv[]) {
    std::vector<GroupId> tmp;

    Memo memo;
    GroupId group_id = 1;
    const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value();
    std::cout << "3.1.->|" << reduced_group_id << ":" << &reduced_group_id << std::endl;

    tmp.push_back(4);
    std::cout << "3.2.->|" << reduced_group_id << ":" << &reduced_group_id << std::endl;

    tmp.push_back(5);
    std::cout << "3.3.->|" << reduced_group_id << ":" << &reduced_group_id << std::endl;
    return 0;
}

结果

3.1.->|1:0x7ffe4fcd3530
3.2.->|4:0x7ffe4fcd3530
3.3.->|5:0x7ffe4fcd3530

问题

2194 次点击
所在节点    C++
29 条回复
ipwx
45 天前
额,楼主你这

std::optional<ReducedGroupId> GetReduceGroupId(const GroupId& group_id) {
// omit
return std::make_optional<ReducedGroupId>(group_id);
}

不是取了 group_id 的地址塞到 optional 里面。optional 本来就是个完整的对象,所以你是复制了一份 group_id 塞到了 optional 里面。

然后

const ReducedGroupId& reduced_group_id

取的就是这个临时的 optional 内部的 int64 的地址,当然这句话执行完就被 “销毁” 了。后面的代码都是错的。
ipwx
45 天前
optional 类似于


template <typename T>
struct Optional {
T* myObject;

Optional() : myObject(nullptr) {}
Optional(const T& value) : myObject(new T(value)) {}
~Optional() { delete myObject; }

bool has_value() { return myObject != nullptr; }
}
Betsy
45 天前
@InkStone 不是哈,g++ 11.4.0 亲测没有报错的。

```bash
g++ a.cc -o a.out -std=c++20 -g -Wall -Werror
```
Betsy
45 天前
@a554340466 这个地址可以发我不?原始出处没找到
Betsy
45 天前
@rabbbit 你这么写肯定是不行的。数值类型为啥没有必要引用呢?
realJamespond
45 天前
右值是个很抽象的东西一直没搞清楚
rabbbit
45 天前
摘自 C++Primer plus

使用引用参数的主要原因有两个。
程序员能够修改调用函数中的数据对象。
通过传递引用而不是整个数据对象,可以提高程序的运行速度。
当数据对象较大时(如结构和类对象),第二个原因最重要。这些
也是使用指针参数的原因。这是有道理的,因为引用参数实际上是基于
指针的代码的另一个接口。那么,什么时候应使用引用、什么时候应使
用指针呢?什么时候应按值传递呢?下面是一些指导原则:
对于使用传递的值而不作修改的函数。
如果数据对象很小,如内置数据类型或小型结构,则按值传递。

既然 std::move 是不行的。
根据我的理解,你是希望修改 group_id 时,跟着变动 reduced_group_id ,对吗?
blinue
44 天前
我在 godbolt 里复现这个问题: https://godbolt.org/z/h45896sM5

只会在 O1 优化下出现,是一个悬垂引用导致的巧合。

1. memo.GetReduceGroupId(group_id) 返回的临时的 std::optional<ReducedGroupId> 存储在 [rsp + 16] 到 [rsp + 24]。optional 本身共 9 个字节,前 8 个字节是 ReducedGroupId ,后跟一个 bool 。

2. reduced_group_id 为 .value() 返回的地址,即 rsp + 16 ,注意这是一个栈上的临时空间,reduced_group_id 为悬垂引用。后续用 rbx 存储 reduced_group_id 引用的地址。

3. tmp.push_back(4) 恰好将 4 ( 8 个字节)存储到 [rsp + 16],覆盖了 reduced_group_id 指向的内存。编译器认为这是安全的,因为临时的 std::optional<ReducedGroupId> 已经析构,这导致后续读取 reduced_group_id 的值为 4 。
a554340466
44 天前
@Betsy https://dokumen.pub/c17-the-complete-guide-396730017x-9783967300178.html

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1062767

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX