Thread: Question about delete function

  1. #1
    Registered User
    Join Date
    Jul 2016
    Posts
    13

    Question about delete function

    Hello every one
    I have some questions about the delete function in this following code.


    Code:
       1 #include <iostream> 
       2 #include <cstring> 
       3 using namespace std; 
       4  
       5 class String 
       6 { 
       7 private: 
       8         char *string; 
       9         long len; 
      10 public: 
      11         String (); 
      12         String (const char *I_string); 
      13         String & operator = (const String &); 
      14         void show_data () const; 
      15         ~String (); 
      16 }; 
      17  
      18 String::String () 
      19 { 
      20         len=0; 
      21         string=new char [len+1]; 
      22         string='\0'; 
      23 } 
      24  
      25 String::String (const char *I_string) : len(strlen(I_string)) 
      26 { 
      27         string=new char [len+1]; 
      28         strcpy(string,I_string); 
      29 } 
      30  
      31 String & String::operator = (const String & astring) 
      32 { 
      33         delete [] string; 
      34         string=new char [astring.len+1]; 
      35         strcpy(string,astring.string); 
      36         return * this; 
      37 } 
      38  
      39 void String::show_data () const 
      40 { 
      41         cout << "the string is : " << string <<endl; 
      42         cout << "the address is : " << (void *) string << endl; 
      43 } 
      44  
      45 String::~String () 
      46 { 
      47         delete [] string; 
      48 } 
      49 int main () 
      50 { 
      51         String A_string ("my string"); 
      52         String B_string; 
      53  
      54         B_string=A_string; 
      55  
      56         A_string.show_data (); 
      57         B_string.show_data (); 
      58  
      59         return 0; 
      60 }


    As shown in the code, it has two objects: A_string and B_string. It uses delete function at line 33 and line 47 to free the memory of string. My question is the delete function at line 33 will free the memory of string which belongs to B_string or the string's memory of A_string and B_string at the same time. I also have the same puzzle at line 47. who can explain them for me.


    Thanks
    fan li

  2. #2
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    *moved to C++ programming forum*

    Quote Originally Posted by fanli
    My question is the delete function at line 33 will free the memory of string which belongs to B_string or the string's memory of A_string and B_string at the same time.
    You wrote string, not astring.string, so it refers to the current object's own member variable named string. If we look back at the copy assignment in main, we see:
    Code:
    B_string=A_string;
    hence the current object is B_string.

    Incidentally, this is a bad implementation of the copy assignment operator: you should not destroy the data until you know it is safe to do so, which in this case means after the new char[astring.len + 1], though more generally it means after the assignment has effectively taken place. Of course, this means that you need another pointer to store the result of operator new[] in the meantime, unless you use more modern methods of implementing the copy assignment operator, which may be beyond the scope of this exercise.
    Last edited by laserlight; 07-17-2016 at 10:42 AM.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  3. #3
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    > 22 string='\0';
    This overwrites your pointer.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  4. #4
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    I'd use rather...

    Code:
    string[0]='\0';
    If you are building a String Class, in the uninitialized constructor, i.e....

    Code:
    String::String () 
    19 { 
    20         len=0; 
    21         string=new char [len+1]; 
    22         string='\0'; 
    23 }
    ...I'd alter the code to perform a minimal allocation, such as 16 bytes or something like that. Further, I'd add another private member variable for the capacity of the String, which will be one character less than the memory allocation (using the char data type). And in every member function you have to do careful 'book-keeping to maintain correct values for the length and capacity, or else you'll crash and burn.

  5. #5
    Registered User
    Join Date
    Jul 2016
    Posts
    13
    Hi laserlight
    Now I understand the delete function at line 33.I still have several questions.


    (1)which object's member variable named string will be deleted by the delete function at line 47 ? I thought both object's member variable named string will be deleted because it is located at destructor, but I am not sure. Could your explain it ?



    (2)The code also uses new function to allocate the memory for the variable named string which belongs to A_string, so the memory should be also freed. If the delete function at line 33 and 47 can not do this, how to free the memory of the A_string's variable named string?


    (3)You said this is a bad copy assignment operator, but I can not fully understand your explanation,
    especially this sentence “Of course, this means that you need another pointer to store the result of operator new[] in the meantime”.
    So if possible, would you mind changing what you said to a simple code to show me a good way to implement copy assignment operator.



    I am a beginner of c++ programming language, so sorry for asking such basic question.


    Fanli

  6. #6
    Registered User
    Join Date
    Jul 2016
    Posts
    13
    Hi freddie
    I can not fully understand what you said because I am a beginner. So could you give me a simple code which perform a minimal allocation?


    Thanks
    Fan Li

  7. #7
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    laserlight means to change the constructor to this:
    Code:
    String & String::operator = (const String& astring) 
    { 
        auto* tmp = new char[astring.len + 1]; 
        strcpy(tmp, astring.string); 
        delete [] string; 
        string = tmp;
        return *this; 
    }
    When you allocate memory, an exception can be thrown. If that happens, you've already deleted your memory, so your string is in an "inconsistent state". Your memory is freed, yet the string class thinks it's not because string points to freed memory and no other members, such as len are updated.
    That's why laserlight suggested you should allocate memory first, copy over the string, and only if that succeeded, then and only then, should you free the memory. That guarantees that you won't get this "inconsistent state."

    What freddie is suggesting is that in your constructor, you do:
    Code:
    String::String () 
    { 
        len = 0; 
        capacity = 16;
        string = new char[capacity + 1];
        string[0] = '\0';
    }
    You currently allocate one character. freddie suggests allocating a few more, e.g. 16.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  8. #8
    Registered User
    Join Date
    Jul 2016
    Posts
    13
    Hi Elysia
    I understand their suggestions. Thank you.
    Fan Li

  9. #9
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    This is the operator= String Assignment implementation I use with my own String Class which I use exclusively in lieu of the C++ Standard Library's String Class...

    Code:
    String& String::operator=(const String& strAnother)
    {
     if(this==&strAnother)                                             // Are You Assigning a String To Itself?
        return *this;                                                  // If So, Simply Return *this.
     if(strAnother.iLen>this->iCapacity)                               // Is The Present String Buffer Large Enough To Accomodate The New String?
     {
        delete [] this->lpBuffer;                                      // If Not, Delete Present Buffer And Get A Larger One.
        size_t iNewSize=(strAnother.iLen*EXPANSION_FACTOR/16+1)*16;    // Round To 16 Byte Alligned Paragraph Granularity
        this->lpBuffer=new TCHAR[iNewSize];                            // Get Larger Buffer.
        this->iCapacity=iNewSize-1;                                    // Set New String::Capacity()
     }
     _tcscpy(this->lpBuffer,strAnother.lpBuffer);                      // Copy New String To this's Buffer.
     this->iLen=strAnother.iLen;                                       // Set String::iLen
    
     return *this;
    }
    Note that my String Class has three member variables as follows...

    Code:
     private:
     TCHAR*    lpBuffer;
     size_t    iLen;
     size_t    iCapacity;
    My uninitialized constructor is simply this...

    Code:
    String::String()
    {
     this->lpBuffer=new TCHAR[16];
     this->lpBuffer[0]=0;
     this->iLen=0;
     this->iCapacity=15;
    }
    In very early implementations of my String Class I only had the char/wchar_t* buffer member, and it worked, but performance was terrible. Caching the length and capacity of the String and religiously maintaining correct values through all String Class manipulations improves performance very much. The EXPANSION_FACTOR equate above I set equal to 2. What that does is double the buffer size for repeated concatenations. It saves memory allocations tremendously. I believe that's the way the C++ Standard Lirary handles it too. Note I've been away from *nix for so long I don't recall if a two byte character encoding is used there. The above code is Windows specific. If I recall *nix used a four byte fixed byte encoding for UNICODE.

    The first step necessary to free oneself from the bloat of the C++ Standard Library is to develop one's own String Class. The second and final step is to eliminate static linkage to the C and C++ runtimes.

  10. #10
    C++ Witch laserlight's Avatar
    Join Date
    Oct 2003
    Location
    Singapore
    Posts
    28,413
    Quote Originally Posted by freddie
    This is the operator= String Assignment implementation I use with my own String Class
    Note that it has the same "destroy before it is safe to do so" bug as fanli's code in post #1. Since the element type itself is ultimately a built-in type rather than a class type whose constructor might throw an exception, you could fix this by using no-throw new instead, though it might be better to just restructure it.

    Quote Originally Posted by freddie
    My uninitialized constructor is simply this...
    I am curious as to why you did not use the initialiser list and also used magic numbers.
    Quote Originally Posted by Bjarne Stroustrup (2000-10-14)
    I get maybe two dozen requests for help with some sort of programming or design problem every day. Most have more sense than to send me hundreds of lines of code. If they do, I ask them to find the smallest example that exhibits the problem and send me that. Mostly, they then find the error themselves. "Finding the smallest program that demonstrates the error" is a powerful debugging tool.
    Look up a C++ Reference and learn How To Ask Questions The Smart Way

  11. #11
    Registered User
    Join Date
    Jul 2016
    Posts
    13
    Hi freddie
    Thanks for sharing your code.

  12. #12
    Registered User
    Join Date
    Dec 2010
    Location
    Trinidad, CO (log cabin in middle of nowhere)
    Posts
    148
    Elysia said…

    When you allocate memory, an exception can be thrown. If that happens, you've already deleted your memory, so your string is in an "inconsistent state". Your memory is freed, yet the string class thinks it's not because string points to freed memory and no other members, such as len are updated. That's why
    laserlight suggested you should allocate memory first, copy over the string, and only if that succeeded, then and only then, should you free the memory. That guarantees that you won't get this "inconsistent state."
    Unless one uses std::nothrow and tests the return from new, new failures seem to simply crash the app. For example, this attempt to allocate 4 billion bytes on 32 bit boxes crashes at the call to new...

    Code:
    // This Crashes!  ( x86 )
    // cl MemTest1.cpp /O1 /Os /GS- /MT /EHsc   
    // 103,936 Bytes
    #include <cstdio>
    
    int main()
    {
     size_t iNumber;
     char* pCh=NULL;
     
     iNumber=4000000000;
     pCh=new char[iNumber];
     printf("pCh = %p\n",pCh);
     if(pCh)
        delete [] pCh;
     else
        printf("Memory Allocation Failed!\n");
     getchar();
     
     return 0;
    }
    At least that's what I'm seeing on Windows 10 when compiling with VC19 (VS 2015) x86. So therefore I don't see any justification for worrying about a string object being in an indeterminate state if the whole app has already crashed. All of which just rephrases the issue.

    The only way I know of to prevent a crash if std::nothrow isn’t being used to handle the bad alloc exception is to use a try/catch exception handling block. Is there any other way? This works (64 bit code; Microsoft Compiler)…

    Code:
    // cl Exceptions.cpp /O1 /Os /MT /GS- /EHs
    // 131,072 Bytes
    #include <cstdio>
    
    int main()
    {
     size_t iNumber=0;
     wchar_t* pCh=NULL;
    
     iNumber=400000000000000000;
     try
     {
        pCh=new wchar_t[iNumber];
        printf("pCh = %p\n",pCh);
     }
     catch(...)
     {
        printf("Memory Allocation Error!\n");
     }
     getchar();
     
     return 0;
    }
    Having said that, I want to thank Laserlight and Elysia for their suggestions on this and having caused me to think about it. That code blurb I posted above is just about the way my String Class has been for like 10 years and has never caused me any trouble. But that’s simply because I very seldom need large strings for the work I do, or allocate so many of them that allocation failures are likely. An allocation failure with my code as I posted it would surely GPF. So I’ve spent the past two days updating my code to make it better, hopefully.

    What I finally decided to do is a bit hard to describe, but here goes. I use both GCC and MS VC, but for MS VC I do some builds with the /nodefaultlib linker settings as I have my own versions of the C and C++ libraries parts of which I’ve written myself, and other parts obtained from reliable sources, and I link against them instead. However, sometimes I do use the standard linkages with MS VC. If I’m linking against my own library which I’ve named TCLib.lib (Tiny C Lib) I can simply use C++ new ‘as is’ because my implementations of operator new and operator new[] are just simple wrappers around Windows Api HeapAlloc(), which returns NULL on allocation failures, and as can be seen below, that NULL is simply returned to the point of call and can be tested…

    Code:
    //=====================================================================================
    //               Developed As An Addition To Matt Pietrek's LibCTiny.lib
    //
    //                          LIBCTINY -- Matt Pietrek 2001
    //                           MSDN Magazine, January 2001
    //
    //                              With Help From Mike_V
    //                       
    //                           By Fred Harris, January 2016
    //
    //                    cl newdel.cpp /GS- /c /W3 /DWIN32_LEAN_AND_MEAN 
    //=====================================================================================
    #include <windows.h>
    
    void* __cdecl operator new(size_t s)
    {
     return HeapAlloc(GetProcessHeap(), 0, s);
    }
    
    void  __cdecl operator delete(void* p)
    {
     HeapFree(GetProcessHeap(), 0, p);
    }
    
    void* operator new [] (size_t s)
    {
     return HeapAlloc(GetProcessHeap(), 0, s);
    }
    
    void operator delete [] (void* p)
    {
     HeapFree(GetProcessHeap(), 0, p);
    }
    So my revised operator= in my String Class now looks like this…

    Code:
    String& String::operator=(const String& strAnother)
    {
     if(this==&strAnother)
        return *this;
     if(strAnother.iLen > this->iCapacity)
     {
        size_t iNewSize=(strAnother.iLen*EXPANSION_FACTOR/MINIMUM_ALLOCATION+1)*MINIMUM_ALLOCATION;
        TCHAR* pCh=NEW TCHAR[iNewSize];
        if(pCh)
        {
           delete [] this->lpBuffer;
           this->lpBuffer=pCh;
           _tcscpy(this->lpBuffer,strAnother.lpBuffer);
           this->iCapacity=iNewSize-1;
           this->iLen=strAnother.iLen;
           this->blnSucceeded=TRUE;
        }
        else
        {
           this->blnSucceeded=FALSE;
        }
     }
     else
     {
        _tcscpy(this->lpBuffer,strAnother.lpBuffer);
        this->iLen=strAnother.iLen;
        this->blnSucceeded=TRUE;
     }
    
     return *this;
    }
    …where I simply test for a non-NULL return from new before doing the delete/copy operation. And that works when built against the standard libraries because I used my NEW macro instead of new for the call, and I defined my macro NEW like so…

    Code:
    #ifdef    TCLib
       …
       #define   NEW new
       …
    #else
       …
       #include  <new>
       #define   NEW new(std::nothrow)
       … 
    #endif
    And I added another private data member variable to my String Class named blnSucceeded, which I can test (there’s an accessor too) if I feel an allocation error would be a possibility, in the same way a try/catch block would be used in a more standard C++ app. I don’t believe C++ Exception Handling would work with my TCLib.lib. Anyway, here’s a 64 bit test app using my String Class and TCLib.lib where I attempt to allocate 32 billion wchar_ts or 64 billion bytes for the text “Hello, World!”…

    Code:
    // cl Demo24.cpp Strings.cpp /O1 /Os /GS- TCLib.lib kernel32.lib user32.lib
    // 5,120 Bytes VC19 (VS 2015) x64 UNICODE
    // Test Machine HP Envy Laptop Windows 10 Home x64 16 Gigabytes RAM
    #define UNICODE
    #define _UNICODE
    #include <windows.h>     //  32,000,000,000, i.e.,  32 billion wchar_ts --   64,000,000,000 bytes succeeds
    #include "stdlib.h"      // 320,000,000,000, i.e., 320 billion wchar_ts --  640,000,000,000 bytes fails
    #include "stdio.h"
    #include "tchar.h"
    #include "Strings.h"
    extern "C" int _fltused=1; 
    
    int main()
    {
     //size_t iNumber   = 320000000000;    // Fails
     size_t iNumber     = 32000000000;     // Succeeds
     String s1(iNumber,false);             // The ‘false’ 2nd parameter just indicates the memory doedn’t have to be zeroed out
      
     if(s1.blnSuccess())
     { 
        s1=L"Hello, World!";
        printf("Allocation Successful!\n");
        s1.Print(L"s1.lpStr() = ",true);
        printf("s1.Capacity() = %Iu\n",s1.Capacity());
        printf("s1.Len()      = %Iu\n",s1.Len());
     }   
     else
     { 
        printf("Allocation Failed!\n");
        printf("s1.lpStr = %Iu\n",s1.lpStr());
     }    
     getchar();
     
     return 0;
    }
    That actually worked. Here’s the output…

    Code:
    Allocation Successful!
    s1.lpStr() = Hello, World!
    s1.Capacity() = 32000000015
    s1.Len()      = 13
    But if I switch comments on my iNumber variable above and attempt to allocate 320 billion wchar_ts I get this (but no crash)…

    Code:
    Allocation Failed!
    s1.lpStr = 0
    The above code compiles to just 5 k with my TCLib!

    So thanks again for the heads up Laserlight and Elysia. I’m happier now. I mostly do Win32 coding and use the memory allocation functions provided there instead of new from C++, and in that context I always test memory allocation failures. But somehow I left that bad usage in my String Class code. The only reason I can come up with for my dereliction in that regard is that when I first started writing this String Class code a long time ago I may have found out that allocation failures weren’t being indicated by a NULL return, but rather by an exception or crash, and perhaps I didn’t know what to do about it, and hoped it wouldn’t ever fail (and it never did). Its only been the past couple years that I learned about the std::nothrow option of new and started using it.

    If you are still following this thread FanLi and are interested in my whole String Class, I just posted my only 2 day old updated version here in post #25…

    Modeless MessageBox() Internals


    Note To Laserlight…

    About the magic numbers…

    They are lapses as you certainly know. I’ve been ripping and tearing at my String Class for 10 years, at least. MINIMUM_ALLOCATION and EXPANSION_FACTOR have always been defined, but in the heat of doing battle its always easier to type 16 or 2 instead. So they seem to have multiplied or crept in some over the years. I fixed them all in my rewrite though!

  13. #13
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    You should always handle exceptions because otherwise your program will terminate (or crash) and some, but possibly not all, destructors will run. That's a great way to introduce uncertainty and bugs into your program. Having an exception safe class also adds another nice feature to your programs: they can be statically verified to behave correctly under all circuimstances (due to exceptions). That's a nice to have, isn't it? Sometimes writing such code is prohibitively expensive or just very complex. But in this case, with a little careful thinking, we get it for free.

    Also, please don't link against your own standard libraries. That's a great way to introduce bugs, crashes or incompatibilities into your program. Use the standard library and introduce your own functions on top of those in a separate code base, dll, static library or what have you. Also use C++ alternatives where possible (e.g. operator new) and don't mix and match (e.g. C++ and Win32). Use one or the other to avoid bugs and problems.

    Also, allocation failures usually are due to your program using too much memory, too much heap fragmentation or just allocating too much memory at once.
    Quote Originally Posted by Adak View Post
    io.h certainly IS included in some modern compilers. It is no longer part of the standard for C, but it is nevertheless, included in the very latest Pelles C versions.
    Quote Originally Posted by Salem View Post
    You mean it's included as a crutch to help ancient programmers limp along without them having to relearn too much.

    Outside of your DOS world, your header file is meaningless.

  14. #14
    Registered User
    Join Date
    Jan 2005
    Posts
    7,366
    fanli, while I saw some good advice, it might be over your head. I will try to answer your questions which I didn't see get answered directly...

    >> (1)which object's member variable named string will be deleted by the delete function at line 47 ? I thought both object's member variable named string will be deleted because it is located at destructor, but I am not sure. Could your explain it ?

    The destructor is called once for each instance of an object you create. In your class, you create A_string and B_string, so the destructor will be called separately for each object. So both A_string and B_string will have their string member variable deleted when their destructor is called.

    >> (2)The code also uses new function to allocate the memory for the variable named string which belongs to A_string, so the memory should be also freed. If the delete function at line 33 and 47 can not do this, how to free the memory of the A_string's variable named string?

    The delete function on line 47 does this. The delete function on line 33 also might do this, depending on whether that function (operator=) is called. In your program, operator= is never called for A_string, so line 33 never deletes that memory and line 34 never allocated new memory for that variable.

    I think (3) was properly answered already.

  15. #15
    Registered User
    Join Date
    Aug 2016
    Posts
    2
    As I know delete [] only delete a pointer on array but not function.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 2
    Last Post: 01-10-2016, 01:23 AM
  2. List - Why my delete function doesn't delete?
    By juanjuanjuan in forum C Programming
    Replies: 7
    Last Post: 12-09-2014, 10:10 PM
  3. BST delete function
    By spikestar in forum C++ Programming
    Replies: 0
    Last Post: 08-31-2010, 08:55 PM
  4. Delete Function for AVL Trees
    By Lost in forum C Programming
    Replies: 5
    Last Post: 08-24-2009, 10:34 AM
  5. does C have a delete function??
    By aspand in forum C Programming
    Replies: 12
    Last Post: 05-17-2002, 03:14 PM

Tags for this Thread