• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
xiajia

about "delete" and "delete []"

35 posts in this topic

code like this:
 

 

 

int * a = new int(0);
int * b = new int(0);
int * c = new int[2];
int * d = new int[2];
int * e = new int[1];
delete a;
delete []b;
delete c;
delete []d;
delete e;

 

The compiler does not prompt "error" or "warning"(vs 2005 at warning level 4).
But such use of "delete" and "delete []" is incorrect according the book(line "delete []b;" and "delete c;").
Is that so?what problem will cause?

0

Share this post


Link to post
Share on other sites
That sort of thing can't be detected at compile-time, in the general case.
Consider:
void foo(int x)
{
    int *p;
    if ((x % 2) == 0)
        p = NULL;
    else if ((x % 3) == 0)
        p = new int(0);
    else if ((x % 5) == 0)
        p = new int[2];

    if ((x % 7) == 0)
        delete p;
    else if ((x % 11) == 0)
        delete[] p;
}
This will produce well-defined behaviour if I pass in say 4, 14, 21, 22, or 55, but will produce undefined behaviour if I pass in say 7, 33 or 35, and will leak memory if I pass in say 3. Edited by iMalc
0

Share this post


Link to post
Share on other sites

i use new[ ] and delete [ ] when i need to  create and delete an allocated array

Edited by ISDCaptain01
0

Share this post


Link to post
Share on other sites

The only difference between delete and delete[] is, that in the latter case the object destructor isn't called for the pointer address only but all subsequent "positions" in the array until the end of the memory block is reached. In theory it should be save to always call delete[] since the memory block would have only sizeof(object). But it's better to follow the rules to ensure compiler compability. You should also consider using dynamic arrays like std::vector which does the gritty things for you.

-3

Share this post


Link to post
Share on other sites

SiCrane mentions some very good points. I'm going to dive a little deeper into the internals of memory management and try to explain why the above is such a big problem.

 

There are various strategies for managing allocated memory. The allocator is what actually allocates the memory and frees it when you call new/delete. One very common allocation strategy is to request memory from the operating system when more is needed, but when its freed, don't immediately hand it back to the operating system. Keep it around. Why? Well, chances are the programmer is going to allocate more memory some time soon. If you give it all back to the OS right now, you'll just have to request more form the OS soon anyway (and these can all be pretty slow operations), so if the allocator hangs onto it, when you request more memory the allocator has some already available to give you. Yay, increase in speed! (there are other practical reasons why, but this is a big one) (and of course, if the allocator has a ton of free memory it's holding onto, it might decide to hand some of it back to the OS, but still keep a "reserve" of free memory hanging around).

 

Now, as you allocate memory and free it, your memory becomes fragmented. If you looked at it as one continuous block of memory, you'd have free chunks and allocated chunks mixed all over. How does the allocator keep track of what's allocated and what's free? Remember, this is the allocator! It can't just ask some other allocator for memory and keep a nice std::vector<ptr> of free and allocated chunks of memory. It's a pain in the butt to do, but here's a common way it's done:

 

Linked lists. In memory. When you allocate memory, the allocator does something like this:

 

void* malloc(size_t bytes) // same idea for new/new[] (horribly simplified, though)
{
    void* ptr = RequestBytesFromOperatingSystem(bytes + HEADER_SIZE);
 
    WRITE_HEADER(ptr, bytes + HEADER_SIZE); // writes header info to ptr, note this is being horribly simplified
 
    return (char*)ptr + HEADER_SIZE; // skip the header and give return the usable block to the user
}

 

 

The allocator allocates an extra few bytes to keep a header for each block of memory. This header usually says how large the block is, if the block is free or allocated and in use, if the next block (at ptr + bytes + HEADER_SIZE) is free or allocated and in use, and if the previous block is free or allocated and in use. If the block is free, the allocator might use some of the space in the block to store pointers to the previous and next free blocks (maintaining a linked list).

 

So now let's take this idea for memory blocks, and what SiCrane said above, and document them to figure out how a block of memory might look to the allocator

 

/*
Potential structure of a memory block allocated by new (that is, this block is allocated and in use):
Header Tag (4 bytes)
    Upper 29 bits: size of this block in memory
    Bit 2: 1 if previous block is free, 0 if previous block is allocated
    Bit 1: 1 if next block is free, 0 if next block is allocated
    Bit 0: 1 if this block is free, 0 if this block is allocated
Pointer into segment table (4 bytes) (also acts as padding to maintain 8-byte alignment, like many CPUs need)
    Instead of wasting the padding, we'll store which segment into our segment table this block should be
    inserted into when its freed (so we don't have compute it when we free it, yay speed!)
User data (N bytes)
    This is the memory the programmer is actually able to use

 
Potential structure of a memory block allocated by new [] (that is, this block is allocated and in use):
Header Tag (4 bytes)
    Upper 29 bits: size of this block in memory
    Bit 2: 1 if previous block is free, 0 if previous block is allocated
    Bit 1: 1 if next block is free, 0 if next block is allocated
    Bit 0: 1 if this block is free, 0 if this block is allocated
Number of elements (4 bytes) (also acts as padding to maintain 8-byte alignment, like many CPUs need)
    Instead of wasting the padding, we'll use it to store the number of elements in this array
User data (N bytes)
    This is the memory the programmer is actually able to use
 

Potential structure of a free block of memory (after it's been freed by delete or delete []):
Header Tag (4 bytes)
    Upper 29 bits: size of this block in memory
    Bit 2: 1 if previous block is free, 0 if previous block is allocated
    Bit 1: 1 if next block is free, 0 if next block is allocated
    Bit 0: 1 if this block is free, 0 if this block is allocated
Pointer to the next free block (4 bytes)
    This way we can maintain a linked list of free blocks
Block data (N bytes)
    Junk
*/
 
There are various allocation strategies and memory block formats, but the above encapsulates some common practices and ideas shared by many allocators.
 
Now, when the allocator tries to free a block of memory, it's going to try to use the block's header (the first 4 bytes of the block), and the following 4 bytes (what those bytes represent depends on whether or not you called new or new [], and depending on whether you call delete or delete [] it will change how those bytes are interpreted). If you call the delete on memory you used new [], the allocator will think the 4 bytes after the header are a pointer into the segment table (which they're not; those 4 bytes represent the number of elements in the array), and boom, you've successfully corrupted your memory (or hopefully crashed). If you call delete [] on memory you used new, the allocator will think the 4 bytes after the header are the number of elements in the array (which they're not; those 4 bytes represent a pointer into the segment table), and boom, you've successfully corrupted your memory (or hopefully crashed).
 
If different block formats and allocation strategies are used, it's possible to completely destroy the allocator's linked list of free/allocated blocks by calling the wrong delete, and now you've got memory leaks, you're overwriting data and double allocating the same blocks of memory for different uses, etc. Bad, bad, bad mojo.
 
Mixing delete with new [] or delete [] with new is not safe! Yes, it's possible the allocator implements new as new [1] (which would be a valid way to do it), and delete as delete [], but banking on that idea is like playing Russian roulette (with a gun that has more than one round in it).
 
Writing a correct, proper, fast, and efficient allocator is very hard work and involves lots of tricks. The last thing you need as an allocator is someone screwing up your tricks and hacks by freeing memory wrong.
Edited by Cornstalks
2

Share this post


Link to post
Share on other sites

It sounds very interesting,to use "new[1]" instead "new" and at the same time use "delete[]" instead "delete".

 

0

Share this post


Link to post
Share on other sites

If I want to implement a memory recovery function. This will become very complicated.

 

void freeMemory(void * p)

{

     //how to do

}

0

Share this post


Link to post
Share on other sites
If I want to implement a memory recovery function. This will become very complicated.

 

void freeMemory(void * p)

{

     //how to do

}

You might want to a) start a new thread, b) specify language, and c) explain things very clearly. Writing a memory allocator is a huge pain in the butt.

2

Share this post


Link to post
Share on other sites

Well, with a specific example to illustrate.Smart pointers.The general method is as follows:

 

 

#define XDELETE(p) { if (p != NULL) { delete p; p = NULL; } }
template<class T> class _XSmartP;
template<class T> class _XBackP 
{ 
private:    
    friend _XSmartP<T>;     
    T *m_p;    
    size_t m_counter;    
    _XBackP(T *p)  
       :m_p(p)  
       ,m_counter(1)     
    {   
        printf("_XBackP constructor called!\n");     
    }     
    ~_XBackP()     
    {         
        XDELETE(m_p);   
        printf( "_XBackP distructor called!\n");     
    } 
};  
template<class T> class _XSmartP 
{ 
public:
    _XSmartP(T *p)
        :m_backP(new _XBackP<T>(p))    
    {   
        printf("_XSmartP constructor called ! use = %d\n",m_backP->m_counter);    
    }      
    _XSmartP(const _XSmartP& temp)  
        :m_backP(temp.m_backP)    
    {
        ++m_backP->m_counter;     
        printf("_XSmartP copy constructor called ! use = %d\n",m_backP->m_counter);    
    }       
    _XSmartP<T>& operator=(const _XSmartP<T>&temp) 
    {   
        if(this == &temp) return *this;  
        ++temp.m_backP->m_counter;   
        if(--m_backP->m_counter == 0)   
        {   
            XDELETE(m_backP);  
        }  
        m_backP = temp.m_backP;  
        return *this;  
    }        
    ~_XSmartP()     
    {   
        printf("_XSmartP distructor called ! use = %d\n",m_backP->m_counter);  
        if(--m_backP->m_counter == 0)   
        {   
            XDELETE(m_backP);  
        }    
    }      
    T *getPtr() const     
    {         
        return 
        m_backP->m_p;     
    }      
    T getVal() const     
    {         
        return *m_backP->m_p;
    }     
    void setVal(T val)     
    {         
        *m_backP->m_p = val;     
    } 
private:     
    _XBackP<T> *m_backP;
};

 

 if use as follow:

 

 

_XSmartP<int> temp(new int[20]);

it will cause errors because of "delete" to "new[]".If you want to avoid this problem, you need to define a similar structure for the array.However, this will have a lot of duplicate code.Is there a way to do it to the best of both worlds?

Edited by xiajia
0

Share this post


Link to post
Share on other sites
  1. Don't use _XSmartP or _XBackP. Identifiers beginning with an underscore followed by an upper case are reserved for the implementation.
  2. This: XDELETE(p) { if (p != NULL) { delete p; p = NULL; } } is useless. delete already checks if the pointer is NULL. Calling delete (or delete []) on a NULL pointer does nothing, and is valid code. No need to check for NULL yourself before deleting.
  3. You may want to consider looking at C++'s smart pointers
  4. Specifically, in C++11 std::unique_ptr was added with partial template specialization to handle both arrays and non-arrays; you may want to look at it (basically, if you specialize things properly, you can say _XSmartP<int> temp(new int); and _XSmartP<int[]> temp(new int[20]); and the right deleter will be called)
  5. Be aware that the usage for single pointers and arrays is a little different (for example, if you have a single pointer, you may not want to provide operator [], but if you have an array, you may want to provide operator [] but not operator * or operator -> (it's up to you in the end, though))
  6. Sometimes, you just have to duplicate more code than you'd really like
2

Share this post


Link to post
Share on other sites

Thank you very much for your advice.
about 2:it's true that "delete" already checks if the pointer is "NULL".(I do not know whether all compilers are like this. But vs2005 is indeed the case.)
but "delete" not set the pointer to "NULL"


 

int *p = new int[100];
delete []p;
delete []p; // this is a runtime error

The actual situation may be more complicated than this.

about 3:Linked content is very good (yet not try to use), but I've seen about "boost::scoped_ptr","boost::shared_ptr","boost::scoped_array","boost::shared_array","boost::weak_ptr","boost:: intrusive_ptr:, so be a bit confusing to me.

Edited by xiajia
0

Share this post


Link to post
Share on other sites
Thank you very much for your advice.
about 2:it's true that "delete" already checks if the pointer is "NULL".(I do not know whether all compilers are like this. But vs2005 is indeed the case.)
but "delete" not set the pointer to "NULL"


 
int *p = new int[100];
delete []p;
delete []p; // this is a runtime error

The actual situation may be more complicated than this.

The fact that it is an error to double-delete something is a good thing, not a bad thing. If you're ever trying to double-delete a pointer, you should want your program to say so immediately, because you have a fundamental design error that has to be fixed. You should never even get to the point in the first place where a pointer can be double-deleted. Setting the pointer to null hides that problem and you may never be aware of it.

0

Share this post


Link to post
Share on other sites
about 2:it's true that "delete" already checks if the pointer is "NULL".(I do not know whether all compilers are like this. But vs2005 is indeed the case.)

The C++ standard requires this. All compilers should do this (and if they don't, it's a very serious bug in the compiler)

 

but "delete" not set the pointer to "NULL"

But to be honest, setting it to NULL here is kinda useless. There are 3 places XDELETE is called: the two destructors and the assignment operator. Setting a pointer to NULL in the destructor is useless because the object is going out of scope anyway. Setting it to NULL in the assignment operator might be useful and help detect accidental dereferencing after it's deleted, but it can also mask double-delete bugs (where the logic of the program isn't very good, and it double deletes the pointer (but since it's NULL the second time, nothing happens)). Setting it to NULL in the assignment operator is something I'd consider okay, but just be aware of its pros and cons.

 

int *p = new int[100];
delete []p;
delete []p; // this is a runtime error

The actual situation may be more complicated than this.

Indeed, it's a runtime error, but I'd argue that code has faulty logic and the error is a good thing that brings the faulty logic to your attention. Hiding the faulty logic only causes headaches down the road. That's why most debuggers set deleted/invalid pointers to some garbage value (like 0xDEADBEEF, 0xFCFCFCFC, 0xFFFFFFFF, etc), so that if you have faulty logic like that, it errors out rather than hides the poor logic.

 

about 3:Linked content is very good (yet not try to use), but I've seen about "boost::scoped_ptr","boost::shared_ptr","boost::scoped_array","boost::shared_array","boost::weak_ptr","boost:: intrusive_ptr:, so be a bit confusing to me.

They're different smart pointers with different usage semantics.  With a little googling, you can find some useful resources explaining some of the differences. Interesting factoid: Boost has shared_ptr as well as shared_array (because of the delete vs delete [] problem you're describing in this thread, and each smart pointer has a few different accessor methods (operator* and operator-> vs operator[]))

2

Share this post


Link to post
Share on other sites

[quote]

It sounds very interesting,to use "new[1]" instead "new" and at the same time use "delete[]" instead "delete".

[/quote]

This would incur unnecessary performance overhead in the "exceptionally common" implementation SiCrane described.

 

[quote]

it will cause errors because of "delete" to "new[]".If you want to avoid this problem, you need to define a similar structure for the array.However, this will have a lot of duplicate code.Is there away to do it to the best of both worlds?

[/quote]

One way is to support the idea of a "custom deleter", like the shared_ptr<>. For example, if you are managing a third party resource, such as SDL_Surfarce, you need to use SDL_FreeSurface(). By supporting a custom deleter, you can support this usage, while also supporting the idea of a "shared array".

 

That said, I concur with Cornstalks' point #5. You might want to have a different client interface for a shared array.

 

One option is to have a private smart container, which supports a custom deleter, and then expose a public shared pointer and shared array classes, which provide a convenient client interface and handle the custom deleter themselves. The latter would be implemented in terms of the former, minimising code duplication.

 

Or as Cornstalks mentions in points #3 and #4, you could use the standard library.

 

[quote]

about 2:it's true that "delete" already checks if the pointer is "NULL".(I do not know whether all compilers are like this. But vs2005 is indeed the case.)

[/quote]

It is required behaviour of a standards conforming compiler. All the popular compilers get this one right.

 

[quote]

but "delete" not set the pointer to "NULL"

[/quote]

 

Neither does XDELETE:

[code]int *one = new int(42); int *two = one; XDELETE(one); XDELETE(two); // Uh oh![/code]

XDELETE does not solve the general problem. There is a body of thought that such "safe delete" macros merely hide a certain category of pointer bug. The real solution is to understand the lifecycle of the objects involved and ensure it gets deleted once and only once.

 

However, if a given pointer is going to be pointed at another object, then it is correct to nullify it. This is not common though.

 

 

If you really do want to use XDELETE, despite Cornstalks' excellent point #2, you can and should provide it as a regular free function rather than a macro:

[code]template<typename T> void XDELETE(T *& pointer) {     delete pointer;     pointer = nullptr; }[/code]

2

Share this post


Link to post
Share on other sites

thanks for Brother Bob,Cornstalks and rip-off.These recommendations so refreshing for me.I should solve the problem instead of ducking the issue about double delete.

 

 

you can and should provide it as a regular free function rather than a macro:

Not "macro higher efficiency than function"?

1

Share this post


Link to post
Share on other sites
Not "macro higher efficiency than function"?

Not really, no. Compilers these days are incredibly smart. They can inline functions if needed (especially little tiny, templated ones like that), just as if it were a macro. However, sometimes if you inline too many things, you just end up bloating your code (which can actually slow you down). By making it a function, you are letting the compiler decide if its better to inline the function or not to (most of the time, for a tiny function like that, it'll inline it). The compiler knows the target system better than you, and can make smart optimization decisions. If you use a macro, you are forcing inlining, which may not be optimal. So from an efficiency perspective, just use a function.

 

However, there's another negative side of using macros. Macros do not respect scoping rules at all, whereas functions do. You can put a function in a namespace, and if you scope things right, you can have multiple functions, all with the same name (this is important so the code you write doesn't conflict with or "override" the code someone else writes, even if you both have a function named "dotProduct" for example). Macros don't do this.

 

And again, another negative of using macros: how their arguments are evaluated. Try the following with the macro, and compare it with a function:

[code]// I know you're code isn't doing something like this, // but it shows another drawback of macros vs functions // Go to http://ideone.com/YWAsoX for the results #include <iostream>   #define MACRO_ABS(p) ((p) < 0 ? -(p) : (p))   template <typename T> T function_abs(T p) {     return p < 0 ? -p : p; }   int main() {     int i = -100;
    std::cout << "macro: " << MACRO_ABS(i++) << std::endl;     std::cout << "i: " << i << std::endl;       i = -100;
    std::cout << "function: " << function_abs(i++) << std::endl;

    std::cout << "i: " << i << std::endl;
}
[/code]
0

Share this post


Link to post
Share on other sites
template<typename T>
void XDELETE(T *&p)
{
    delete p;
    p = NULL;
}

template<class T> class tempClassA;
template<class T> class tempClass
{
private:
    friend tempClassA<T>;
    T * m_p;
    tempClass()
        :m_p(NULL)
    {
    }
    ~tempClass()
    {
        XDELETE(m_p);
    }
};
template<class T> class tempClassA
{
public:
    tempClassA()
        :m_a(new tempClass<T>())
    {
    }
    ~tempClassA()
    {
         XDELETE(m_a);
    }
private:
    tempClass<T> * m_a;
};

int main()
{
    tempClassA<int> a;
    return 0;
}

compile error. sad.png

Edited by xiajia
0

Share this post


Link to post
Share on other sites
compile error. sad.png

What compiler error? If you can't find the time to report the problem correctly and completely, I can't find the time to help you.
0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0