3

Why this code snippet ouputs a lot of "here"?

I think the program should terminate when after throw std::invalid_argument( "fool" ); has been called.

#include <memory>
#include <iostream>

void* operator new(std::size_t size)
{
    std::cout << "here" << std::endl;
    throw std::invalid_argument( "fool" );   //commit out this, there would be many many ouputs
    return std::malloc(size);
}

void operator delete(void* ptr)
{
    return free(ptr);
}


int main()
{
    //std::unique_ptr<int> point2int(new int(999));

    int* rawpoint2int = new int(666);
}
sunshilong369
  • 646
  • 1
  • 5
  • 17
  • Strolling through this bit of code with a debugger should answer the question very quickly. The tricky bit would be figuring out what in `invalid_argument` is calling `new`. – user4581301 Mar 09 '22 at 00:11
  • @user4581301 I see, then why there are so many outputs?You see `std::invalid_argument` only calls `operator new` once. – sunshilong369 Mar 09 '22 at 00:32
  • 4
    @sunshilong369 That call to `operator new` calls `operator new` again constructing the next `std::invalid_argument` – Artyer Mar 09 '22 at 00:34
  • @Artyer "*`std::invalid_argument` only calls `operator new` once*" - if so, then you have a recursive loop. You call `new`, which throws `invalid_argument`, which calls `new`, which throws `invalid_argument`, which calls `new`, ... – Remy Lebeau Mar 09 '22 at 00:35
  • @Remy Lebeau I see, thanks to all of you. I rewrite the code as `throw std::invalid_argument();`, then there is no recursive loop any more. – sunshilong369 Mar 09 '22 at 00:39
  • @sunshilong369 Sure, you can do that, and then your program won't compile. No program to run, no re-entrant throws in operator new! ;) – paddy Mar 09 '22 at 00:41
  • Why do you have `return std::malloc(size);` after a `throw`? That line will never be executed. – Jesper Juhl Mar 09 '22 at 00:46
  • @JesperJuhl It's just an exercise which could be seen at https://www.datasim.nl/application/files/6815/3777/1447/Exercise_3_Smart_Pointers.pdf, see the first question. – sunshilong369 Mar 09 '22 at 00:56
  • That's strange. Nowhere in that linked pdf is there the suggestion that you should overload `operator new` and throw an exception from it, let alone an exception like `std::invalid_argument`. The whole point of the exercises is to observe what happens when you throw inside a scope context where you have allocated memory, and compare the behavior between a managed pointer and a raw pointer. – paddy Mar 09 '22 at 01:12
  • Side note: That link seems to be pushing `shared_ptr` pretty hard. `shared_ptr` should be used when you have more than one [owner of a resource](https://stackoverflow.com/questions/49024982) or `weak_ptr` dependents. Not only does the sharing incur some overhead, it also tells the reader what to expect about how the resource is managed and they will write code accordingly. Tell people to write the wrong code and you're going to have inefficiencies and bugs. – user4581301 Mar 09 '22 at 01:16
  • Typically you start with a `unique_ptr`, a single owner, and move to `shared_ptr` only when you prove that shared ownership describes the behaviour you need better. – user4581301 Mar 09 '22 at 01:16
  • 1
    @paddy ***to observe... throw inside a scope context where you have allocated memory,*** I misunderstood it, I thought I have to throw an exception in the overload function of `operator new` to observe the memory leak (which is **not possible** for my code snippet since `malloc` is never called). If I understand you correctly, I should throw an exception after the allocation, something like [`{int ptr=new int(9); throw throw std::invalid_argument( "fool" );}`](https://godbolt.org/z/5xbcd38fs), and ***overload `operator delete` to observe the memory leak***. Am I right? – sunshilong369 Mar 09 '22 at 01:36
  • 1
    Correct: you misunderstood, and your new understanding is on the right track. You don't need to throw `invalid_argument` (or any standard library exception for that matter) -- just `throw "fool";` is fine. If instead of allocating an `int` pointer you define a class that outputs a message in its constructor and destructor and then allocate that (either raw or in a managed pointer), you will be able to compare the two behaviors and see the unmanaged allocation does not get released when unwinding the stack after an exception is thrown. – paddy Mar 09 '22 at 01:45
  • @paddy ***If instead of allocating an int pointer you define a class that outputs a message in its constructor and destructor and then allocate that (either raw or in a managed pointer)*** Yep,it's better. I also find that overload the global `operator delete` to observe memory leak is a bad idea(i.e. can't call the standard `operator delete` to actually free the memory). – sunshilong369 Mar 09 '22 at 01:58

1 Answers1

8

The documentation for std::invalid_argument holds the clue:

Because copying std::invalid_argument is not permitted to throw exceptions, this message is typically stored internally as a separately-allocated reference-counted string. This is also why there is no constructor taking std::string&&: it would have to copy the content anyway.

You can see that the string argument is copied by design. This means that re-entering new is pretty much guaranteed if you are throwing this exception in this way.

You should also be aware that malloc can return nullptr which will violate the design of operator new which should either return a valid pointer or throw.

The normal kind of exception to throw in this case is std::bad_alloc. I cannot imagine why you are wanting to throw std::invalid_argument. I thought perhaps you were encountering this issue in a constructor somewhere and decided to test the allocation itself.

Technically you can resolve the problem by passing a default-constructed string as the argument:

// ... but, why would you do this?!! :(
throw std::invalid_argument(std::string());  // will not allocate

Eww, yucky. I recommend you find a more appropriate exception to throw (if you actually need one), or create your own non-allocating exception.

paddy
  • 60,864
  • 6
  • 61
  • 103