1

I am using bcc32 command line compiler from Borland Embarcadero. Consider this program:

    int main(int, char **)
    {
        try
        {
            std::string *a = new string(0xf0000000, ' ');
            ...
            delete a;
        }
        catch(const std::bad_alloc &)
        {
            ...
        }
    }

When the std::string constructor throws a memory exception, the stack is unwound and control is passed to the catch-block. Gnu compilers build in code to delete the memory allocated for the std::string object 'auto-magically', as was stated by someone who commented on the answer in Who deletes the memory allocated during a "new" operation which has exception in constructor? which I wrote. I ran the program in http://ideone.com/IRxHX and the result is that nobody frees the memory allocated by 'operator new' if an exception is thrown before the result of 'new' is stored in an lvalue. In the above case the variable 'a'.

Questions are: 1 Is there a way to delete the memory generated by 'new' in case of an exception, as a part of the stack unwind procedure? 2 What does the C++ standard demand from compilers in this case

Community
  • 1
  • 1
bert-jan
  • 958
  • 4
  • 15
  • 5
    Borland's C++ compilers are too broken. Avoid it if you can. – wilx Jul 11 '11 at 08:37
  • 1
    I am (appart from this issue) completely satisfied about Turbo C++ Explorer. It is free and works conveniently. OK, the command line linker is not really fool proof, but I forgive that. – bert-jan Jul 11 '11 at 12:41
  • See my answer - tested with C++ Builder 2010 and there's no such problem. Also, your QC test case is borked. – Zach Saw Jul 13 '11 at 01:11

3 Answers3

2

Either your compiler is broken, or something else funny is going on. The implementation is required to free the memory:

5.3.4/17:

If any part of the object initialization described above terminates by throwing an exception and a suitable deallocation function can be found, the deallocation function is called to free the memory in which the object was being constructed, after which the exception continues to propagate in the context of the new- expression. If no unambiguous matching deallocation function can be found, propagating the exception does not cause the object’s memory to be freed.

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Can anyone confirm that Borland c++ compiler authors have intended to meet and actualy have met this requirement? I've had the same problem with microsoft's VC++ compiler, but that was 10 years ago. – bert-jan Jul 11 '11 at 08:40
  • The obvious issue here it the *unambiguous matching deallocation function*. Let us hope that true compilers will diagnose the absence / ambiguity at compile time! – Matthieu M. Jul 11 '11 at 08:43
  • Can dll-linkage specification difference be the problem? – bert-jan Jul 11 '11 at 08:49
  • @bert-jan: I suppose it's conceivable that you've managed to make the "default" `operator delete` ambiguous via linkage, but I don't know whether it's actually possible in Borland or not. As Matthieu says, you'd kind of hope the compiler would diagnose that. – Steve Jessop Jul 11 '11 at 08:58
  • Can it be the compiler generates code to call a different deallocation routine so that my 'void operator delete(void *)' declared in global namespace is bypassed? std::string does not have an overloaded new or delete member function, so new/delete in global namespace is the thing to deallocate std::string objects (is not it) – bert-jan Jul 11 '11 at 08:59
  • @bert-jan: yes, and if it's calling your global `operator new` then it has to call your `operator delete`, since in general no other `delete` knows how to free that memory. If both of them are in a different dll from `main`, with appropriate dllimport declarations, I'd expect them either both to be found, or neither, but of course the standard doesn't say anything about dlls. – Steve Jessop Jul 11 '11 at 09:01
  • I'm not sure this rules applies fully here. Based on the wording if you call `new` inside your constructor and assign it to plain pointer, then an exception is thrown, there is no requirement to `delete` that pointer, since that object was not involved in the exception. – edA-qa mort-ora-y Jul 11 '11 at 11:16
  • Not true! If I use some kind of a smart pointer, it is taking care of my pointer ONLY after new expression hass fully evaluated and no exception was thrown. Then the constructor of the smart pointer (or an assignment operator) is called. Before that, the smart pointer can not now the address of the memory it should free. – bert-jan Jul 11 '11 at 11:33
  • @edA-qa mort-ora-y: But in this example code, the new-expression is not in a constructor, it's called in `main`. If the `...` code in the try block throws an exception, then the string is leaked. When 5.3.4/17 says "the object initialization described above", it means the constructor that's called as part of a new-expression. It doesn't mean any constructor that the new-expression happens to be inside, you're on your own there, so the rule does "appy fully". – Steve Jessop Jul 11 '11 at 11:42
  • @Steve, the string ctor obviously has a call to `new` in it in order to allocate the memory. After that `new` returns successfully, the ctor might do something else that causes an exception. The result of that first call to `new` will not be deleted in this case. It is the type of error that one can easily make in a constructor (though it'd still be a defect for a standard library class to have this behaviour). That is, from the example I can't tell if the `string` isn't being deallocated, or some internal memory in the string. – edA-qa mort-ora-y Jul 11 '11 at 11:47
  • @edA-qa mort-ora-y: oh, I see what you mean. I doubt that the allocation in the string's constructor succeeds, since it's for `0xf0000000` bytes. But if it does, then I agree it is the responsibility of the `string` class to ensure that it's not leaked. If that block is leaked, but the block containing the `string` is freed, then it looks like a bug in the `string` implementation and nothing to do with `operator delete`. – Steve Jessop Jul 11 '11 at 11:49
  • @edA-qa mort-ora-y: The failing memory allocation is not the initial new std::string performed by main, because a std:s:string is at most a size 50 bytes. The std::string contructor allocates space for a string of 0xf0000000 spaces. That is what causes the memory exception. I can not know the address of the string object, until it is assigned to 'a' in 'main'. – bert-jan Jul 11 '11 at 11:56
  • @Steve Jessop, if the string contructor encounters a memory exception, the corresponding ~string destructor will not be called. Only the destructor of the base class and its members (if any), but that is not the problem. It is not the responsibility of std::string members to free their own this pointer. If noting is done, std::string does and can not known if it is being instanciated as a stack variable (on the stack), as a member of some other object, or on the heap(dynamically allocated). – bert-jan Jul 11 '11 at 12:15
  • Allocating 0xf0000000 bytes seemed like a funny way to cause a bad_alloc & exception, but I see some people are confused, because there are two memory allocations, and only the first succeeds and must be undone. – bert-jan Jul 11 '11 at 12:18
  • I posted this question as a bug report to embrcadero and hope they will put the issue on their to-do list. – bert-jan Jul 11 '11 at 12:35
  • @bert-jan, I do understand, I'm proposing the possibility that the broken string class is in fact doing *two* allocations in its constructor and the second one is failing -- this would cause the loss of the first bit of memory, even if your outer call to `new` was properly undone. This is quite possible since many string implementations do a copy-on-write via a hidden common instance. – edA-qa mort-ora-y Jul 11 '11 at 14:58
  • It is not the string constructor messing up. The string constructor allocates a tremendous amount of memory, at least tries to. The allocation size is close to the entire address space of a 32-bit system. You might replace the new std::string(0xf0000000, ' ') statement by a 'new whatever_class()' statement. If whatever_class::whatever_class() throws an exception, the compiler flaw will cause the memory occupied by the object to be lost. It is now borland's turn to do their say. I'm working on on my project. I've spend a whole day on this issue. I'm tired and want to go home :-) – bert-jan Jul 11 '11 at 17:32
  • stackoverflow.com is a friendly place, and I know what I am doing! – bert-jan Jul 13 '11 at 08:33
2

I tested your sample code with the following, stepping through the code to make sure it all works fine (I took your QC code and modified it to make it work as intended):

#include <tchar.h>
#include <string>
#include <iostream>

int count = 0;
bool start = false;

// from your QC code, modified to count instead of std::cout
void *operator new(size_t cbytes)
{
    void *retval = std::malloc(cbytes);
    if (retval == NULL && cbytes != 0) throw std::bad_alloc();
    if (start) count++;
    return retval;
}

// from your QC code, modified to count instead of std::cout
void operator delete(void *block)
{
    if (block != NULL)
    {
        std::free(block);
        if (start) count--;
    }
}

int _tmain(int argc, _TCHAR* argv[])
{
    try
    {
        start = true;
        std::string *a = new std::string(0xf0000000, ' ');
        // ....
        delete a;
    }
    catch(const std::bad_alloc&)
    {
    }
    std::cout << count;
    return 0;
}

I get 0 for count indicating that the partially constructed string does get its memory freed. Tested with MSVC++ 2005 / 2010 with the same results.

Tested with C++ Builder 2010 (command line: bcc32 program.cpp). It is consistent with the C++ standards.

EDIT: Ah, finally saw that your QC report specifies the usage of dynamic RTL with the command line bcc32 -WCR program.cpp. And yes, when compiling with dynamic RTL, I see the problem. Even then, my test case would've been much better at show casing the problem.

Zach Saw
  • 4,308
  • 3
  • 33
  • 49
  • I doubt if your test case is clearer than mine. I would say they are both fine. Output to cout is from the new/delete operators does not change the control flow. I suspect that in this case, the stack unwind procedure calls some other delete operator. Ambiguity between delete operators due to dll linkage issues could be the cause of this. In case of such an ambiguity, c++ compilers are allowed to skip the delete operator without warning. – bert-jan Jul 13 '11 at 07:55
0

The C++ standard demands that the delete will be called by the compiler.

The C+ standard section which addresses this is:

15.2 Constructors and destructors

1 As control passes from a throw-expression to a handler, destructors are invoked for all automatic objects constructed since the try block was entered. The automatic objects are destroyed in the reverse order of the completion of their construction.

2 An object that is partially constructed or partially destroyed will have destructors executed for all of its fully constructed base classes and non-variant members, that is, for subobjects for which the principal constructor (12.6.2) has completed execution and the destructor has not yet begun execution. Similarly, if the non-delegating constructor for an object has completed execution and a delegating constructor for that object exits with an exception, the object’s destructor will be invoked. If the object was allocated in a new-expression, the matching deallocation function (3.7.4.2, 5.3.4, 12.5), if any, is called to free the storage occupied by the object.

3 The process of calling destructors for automatic objects constructed on the path from a try block to a throwexpression is called “stack unwinding.” [ Note: If a destructor called during stack unwinding exits with an exception, std::terminate is called (15.5.1). So destructors should generally catch exceptions and not let them propagate out of the destructor. —end note ]

Alok Save
  • 202,538
  • 53
  • 430
  • 533
  • Maybe I ought to adres my question to borland/embarcadero because my compiler fails to call de deallocation routine. But what happens if the compiler can not find an unambiguous deallocation routine. Can anyone give an example of such a case? – bert-jan Jul 11 '11 at 08:46
  • @bert-jan: You should address the question to borland/embarcadero with quotations from the C++ Standard and find their say about the issue. Maybe they are already aware of the issue maybe not. You won't know unless you ask. – Alok Save Jul 11 '11 at 08:52
  • bert-jan: This issue has been solved a long time ago AFAIK. In fact, I did a quick test and couldn't find the problem you're describing. – Zach Saw Jul 13 '11 at 01:19