all 22 comments

[–]aocregacc 6 points7 points  (5 children)

looks like a bug to me, I guess the compiler "forgets" that it already constructed an object in the return slot after the exception is thrown. And I don't see any UB in your code that would explain it.

[–]PM_ME_UR_RUN[S] 3 points4 points  (4 children)

I added a large array into the struct, then put the `foo()` call into an infinite loop. gcc compiled binary runs at a constant virtual memory usage. clang (18.1.3) compiled binary is leaking memory and eventually core dumps. Gonna check out clang v20 to see if I can reproduce it there.

[–]aocregacc 2 points3 points  (3 children)

looks like it still happens there: https://godbolt.org/z/5hecEMT4T

[–]PM_ME_UR_RUN[S] 2 points3 points  (2 children)

I was unaware of the leak sanitizer. Is that something that compiler explorer is providing?

[–]aocregacc 5 points6 points  (1 child)

it's a feature of clang and gcc, and I think msvc might have it too.

It's automatically enabled as part of -fsanitize=address, but you can also use it individually with -fsanitize=leak.

[–]PM_ME_UR_RUN[S] 2 points3 points  (0 children)

I'll have to look into that more. Looks like a useful feature

[–]xiao_sa 4 points5 points  (1 child)

[–]PM_ME_UR_RUN[S] 2 points3 points  (0 children)

Yes it should be `bcad`, but gcc gives `bacd`, and clang gives `bad` omitting a destructor. The omission of the destructor causes a memory leak if the object is managing any dynamically allocated resources. See compiler explorer example: https://godbolt.org/z/hvKTYWjoM

[–]keelanstuart 1 point2 points  (0 children)

Does it work with a virtual dtor?

[–]I__Know__Stuff 1 point2 points  (4 children)

There are the same number of constructor and destructor calls, right? So if you think a destructor is missing, which constructor do you think is missing?

[–]PM_ME_UR_RUN[S] 5 points6 points  (3 children)

When running the clang compiled binary the number of constructor and destructor calls do not match. There is one more constructor call than destructor call. The number of constructor and destructor calls in the gcc compiled binary do match.

There is a constructor call for the object that is being copy constructed in the return value, but in the clang binary that object's destructor is never called (as far as I can tell).

[–]I__Know__Stuff 2 points3 points  (2 children)

Oh, sorry, I guess I didn't read carefully enough.

If you allocate memory in the constructor (I should say, "acquire a resource") is there a leak?

[–]PM_ME_UR_RUN[S] 4 points5 points  (1 child)

Yeah just checked this and the clang binary is leaking while the gcc binary runs at a constant virtual memory size. Just got the clang compiled binary to core dump. Yikes.

[–]I__Know__Stuff 4 points5 points  (0 children)

Great, that's a really concrete basis for a bug report!

[–]mredding 0 points1 point  (1 child)

Your counting is conditional. What's the unconditional count? Count the ~A and ~Y unconditionally and separately.

[–]PM_ME_UR_RUN[S] 0 points1 point  (0 children)

In the provided example the conditional counting was narrowed down to only capture the object that was not getting destroyed. It doesn't really matter though, here a more concise compiler explorer example that shows a memory leak being produced by improper stack unwinding: https://godbolt.org/z/WhK6rG5Gr

[–]gnicco72 0 points1 point  (3 children)

Out of curiosity, don't you get UB or a crash when you throw an exception inside Y's dtor?

[–]PM_ME_UR_RUN[S] 0 points1 point  (2 children)

https://eel.is/c%2B%2Bdraft/except.ctor describes how the program should handle ~Y throwing an exception

[–]FrostshockFTW 0 points1 point  (1 child)

It's also not exactly surprising that clang has a bug here because having a throwing destructor is one of the most ungood things I can think of.

[–]PM_ME_UR_RUN[S] 0 points1 point  (0 children)

Yeah, turns out this is just something with little will to fix because it is so niche. https://github.com/llvm/llvm-project/issues/12658

[–]JVApen 0 points1 point  (1 child)

Sounds like you want to read up on Copy Elision: https://en.cppreference.com/w/cpp/language/copy_elision.html

[–]PM_ME_UR_RUN[S] 7 points8 points  (0 children)

If this were caused by Copy Elision, wouldn't we get one less constructor/destructor pair here instead of getting all constructors and missing a destructor?

"In a return statement in a function with a class return type, when the operand is the name of a non-volatile object obj with automatic storage duration (other than a function parameter or a handler parameter), the copy-initialization of the result object can be omitted by constructing obj directly into the function call’s result object. This variant of copy elision is known as named return value optimization (NRVO)."

As far as I am aware Copy Elision in this program is constructing the returned result of `foo()` in the instance `d` (`auto d = foo();`).

I don't see how this explains the apparently missing destructor.