0

As far as my experience goes, a breakpoint on a given line of code means that the first time the debugger breaks on that line, nothing of that line has been executing yet. Or, in other words, when the debugger stops at a breakpoint, it stops there before executing that line of code, not after it or in the middle of it.

And I don't think I had ever seen this view to be contraddicted (despite my experience is mostly on C++, and so that's what I've been always debugging).

However, this seems not to be the case when coroutines are involved.

Take this code from a previous question of mine (it is broken because the code has UB, but the issue I'm describing occurs before that is triggered, so it doesn't matter), a target excerpt of which is here:

class [[nodiscard]] CoroTaskSub {
  …
 public:
  …
  CoroTaskSub(auto h)
   : hdl{h} {
  }
  …
};
CoroTaskSub coro() {
  …
}

CoroTaskSub callCoro() {
  std::cout << "  callCoro(): CALL coro()\n";
  co_await coro();
  …
}

I've done the following:

  1. put a breakpoint at the cout line
  2. put a breakpoint at the co_await line
  3. run the program (at this point the debugger stops at the first of those two breakpoints)
  4. put a breakpoint at the line : hdl{h} {, i.e. in the constructor of the CoroTaskSub class¹
  5. continue with the program

At this point I expected that the debugger woudl break at the breakpoint on the co_await line, before anything of that line is evaluated, but instead it breaks a the breakpoint on CoroTaskSub's constructor, just like the coro() is being processed already.

That's a bit weird, no?

Do you know why that happens?


(¹) Because of how the program is structured, the control has already passed once here, upon the call callCoro() that is done in main, but I've put the breakpoint only here because I wanted to see when this constructor is called to construct coro()'s CoroTaskSub.

Enlico
  • 23,259
  • 6
  • 48
  • 102

1 Answers1

1

Unlike in a regular function, in a coroutine function, creating the return value object is the first thing that happens (well, after creating the promise and a few book-keeping things). This is before any line of code in the coroutine, before even the initial_suspend point. This is done by calling the promise's get_return_object function.

This is necessary because the return value object needs to be a valid object to the caller in the event that the initial_suspend point suspends.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • That tells me why `callCoro()`'s `CoroTaskSub` gets created even before the execution hits the `cout` line. But it doesn't tell me why `coro()`'s `CoroTaskSub` is constructed even before I hit the line `co_await coro();`. I mean I'm not saying that I should enter `coro`'s body before the creation of its `CoroTaskSub`, but that I expected the debugger to stop at the `co_await` line before _anything_ about it is even considered. – Enlico Mar 27 '23 at 08:45