求教个神奇的 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 条回复
zhouxiaoyuan
46 天前
不是 c++神奇,是没管理好对象的生命周期,不能引用临时变量 GetReduceGroupId 。

const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value();
Betsy
46 天前
@zhouxiaoyuan GetReduceGroupId() 返回值不用 optional 修饰,却又没问题。
Donaldo
46 天前
```c++
const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value();
```
华点在这一行,memo.GetReduceGroupId(group_id)是个临时的右值,你取完就悬垂了,所以这个这时候他的值是个 UB 。具体是什么全看编译器实现:
- Apple M2 aarch64+ clang16: 1, 1, 1
- Windows x86 + msvc14: 1, 1, 1
- Linux x86 + gcc14: 1, 4, 5

想要避免 UB ,多加一行把这个临时值存起来就好了
```c++
auto rgi = memo.GetReduceGroupId(group_id);
const ReducedGroupId& reduced_group_id = rgi.value();
```
这里 auto 类型可以是 std::optional<ReducedGroupId>也可以是它的右值引用 std::optional<ReducedGroupId> &&。
chrisyunhua
46 天前
去掉 & 改为 `const ReducedGroupId id = ...` 也可以。

@Betsy 不用 optional 没问题,我的理解是:因为 GetReducedGroupId() 属于 prvalue ,赋给 const & 时触发了 lifetime extension ;而 optional.value() 返回类型是 const & 属于 lvalue ,不触发 lifetime extension 。

https://en.cppreference.com/w/cpp/language/lifetime
noahlias
45 天前
我怎么觉得是你的编译器问题 或者编译 option 的问题

https://godbolt.org/z/4b6x9WKaE

我看这里返回结果是一样的
```
ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
3.1.->|1:0x7ffdce1d3300
3.2.->|1:0x7ffdce1d3300
3.3.->|1:0x7ffdce1d3300
```
blinue
45 天前
未定义行为就是编译器怎么做都可以,有一篇很好的博客 https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633
bfjm
45 天前
@Betsy 不用 optional 修饰 没问题 应该是提升了返回值的生命周期了(const)
bfjm
45 天前
这里应该主要的问题是 optional 被析构了,你还在拿他的一个成员变量的引用,所以出现了垂悬引用,这里的解决方案可以参考前面几楼的答案,还可以提升 optional 的返回值的生命周期
bfjm
45 天前
@bfjm 去掉.value()
InkStone
45 天前
试了一下编译时候有 warning 的……都不用开-Wall

test.cpp:21:46: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value();
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

写 C++的时候-Wall -Werror 是个好习惯
rabbbit
45 天前
visual studio 输出的都是 1
这么写行吗?
const ReducedGroupId&& reduced_group_id = std::move(memo.GetReduceGroupId(group_id).value());
rabbbit
45 天前
额数值有必要用引用吗?
fighterhit
45 天前
人生苦短,学点别的吧
MoYi123
45 天前
编译有警告
main.cpp:43:46: warning: object backing the pointer will be destroyed at the end of the full-expression [-Wdangling-gsl]
const ReducedGroupId& reduced_group_id = memo.GetReduceGroupId(group_id).value();

运行有 asan
==7760==ERROR: AddressSanitizer: stack-use-after-scope on address 0x00016ba93320 at pc 0x00010436df8c bp 0x00016ba93250 sp 0x00016ba93248
READ of size 8 at 0x00016ba93320 thread T0
#0 0x10436df88 in main+0x610 (a.out:arm64+0x100001f88)
#1 0x187dbe0dc (<unknown module>)

Address 0x00016ba93320 is located in stack of thread T0 at offset 192 in frame
#0 0x10436d984 in main+0xc (a.out:arm64+0x100001984)

This frame has 7 object(s):
[32, 40) 'ref.tmp.i.i124'
[64, 72) 'ref.tmp.i.i103'
[96, 104) 'ref.tmp.i.i'
[128, 152) 'tmp'
[192, 208) 'ref.tmp' <== Memory access at offset 192 is inside this variable
[224, 232) 'ref.tmp15'
[256, 264) 'ref.tmp28'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-use-after-scope (a.out:arm64+0x100001f88) in main+0x610
sanbuks
45 天前
@rabbbit 没有必要,直接复制
sanbuks
45 天前
template< class T >constexpr std::optional<std::decay_t<T>> make_optional( T&& value );

引用被 decay 了,直接赋值接收就行了
araraloren
45 天前
Very cool feature, thanks for share this.
a554340466
45 天前
读一下 C++17 The complete guide 的 optional 那一章
a554340466
45 天前
it is always safe to assign any optional return value to a new object:
auto a = getString().value(); // OK: copy of contained object or exception
However, using the returned value directly (other than passing it as an argument) is a source of trouble:
```cpp
auto b = *getString(); // ERROR: undefined behavior if std::nullopt
const auto& r1 = getString().value(); // ERROR: reference to deleted contained object
auto&& r2 = getString().value(); // ERROR: reference to deleted contained object
```
The problem with the references is that by rule, they extend the lifetime of the return value of value() but
not the lifetime of the optional object returned by getString(). Thus, r1 and r2 refer to values that no
longer exist and using them results in undefined behavior.
ElevenQAQ
45 天前
@Donaldo 哥们儿的解释简洁清晰

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

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

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

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

© 2021 V2EX