co_await在MSVC中出现异常结果:是编译器Bug还是设计存在UB?
问题分析:Event协程组件在GCC与MSVC的行为差异
核心原因:未初始化变量导致的未定义行为(UB)
你的代码中存在两处明确的未定义行为,其中第一处直接导致了GCC和MSVC的行为差异:
1. 未初始化的triggered成员
Event结构体中的bool triggered;没有显式初始化。当Awaitable::await_ready()读取这个值时,未初始化的bool值在不同编译器/编译环境下可能是true或false:
- GCC环境中该值恰好为
false,因此await_ready()返回false,协程进入挂起流程,调用await_suspend。 - MSVC环境中该值恰好为
true,因此await_ready()返回true,协程直接跳过挂起,调用await_resume。
2. 悬空的Awaitable指针
operator co_await()返回的是临时Awaitable对象,而你在await_suspend中将这个临时对象的指针存入Event的awaitables集合。临时对象会在co_await表达式执行完成后被销毁,导致awaitables中存储的是悬空指针。后续调用trigger()时,通过悬空指针访问awaiter成员会触发严重的未定义行为。
修复方案
第一步:修复未初始化变量
给Event的triggered成员添加显式初始化:
struct Event{ void trigger(); auto operator co_await(); private: class Awaitable; std::set<Awaitable*> awaitables; bool triggered = false; // 显式初始化,确保初始状态为未触发 };
第二步:修复悬空指针问题
调整Awaitable的设计,让其生命周期与协程挂起周期绑定。可以将Awaitable定义在operator co_await()内部,让协程帧持有该对象,避免临时对象销毁导致的悬空:
auto Event::operator co_await() { struct Awaitable { explicit Awaitable(Event* e) : evt(e) {} bool await_ready() const noexcept { return evt->triggered; } void await_suspend(std::coroutine_handle<> h) { std::cout << "await_suspend\n"; awaiter = h; evt->awaitables.insert(this); // 此时this指向协程帧中的对象,不会悬空 } void await_resume() {std::cout << "await_resume\n"; } bool await_must_resume() const noexcept { return false; } private: friend Event; Event* evt; std::coroutine_handle<> awaiter; }; return Awaitable{this}; }
总结
你遇到的编译器行为差异是未定义行为导致的结果,而非编译器Bug。修复上述两处问题后,GCC和MSVC的执行逻辑会保持一致,同时避免后续可能出现的崩溃或异常行为。
内容的提问来源于stack exchange,提问作者Gene
相关产品推荐
相关产品推荐

