Thread: variable array size

  1. #16
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    Erm, by default... By default you should never use realloc in C++ since it can destroy objects or make them not work the way they should.
    But sure, if you know the objects will be fine after using realloc, you can use it. No one is forcing you not to.
    But the ideal solution (at least on Windows) would be to simply expand the virtual memory beyond the end of the array so moving isn't necessary (as I did with my CArray experiment).
    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.

  2. #17
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Sure, if you know ahead of time the max size your vector will need, you'd ideally want to call v.reserve().

    BTW, how can realloc() destroy objects? It doesn't know anything about objects. If it can't expand the current memory, it allocates new memory somewhere else and does a memcpy() from the old location to the new one.
    Obviously that could cause some objects to barf if they rely on pointers to themselves and those pointers don't get updated when they were moved...

    In any case, I doubt the performance benefit would be noticable in most cases.

  3. #18
    C++まいる!Cをこわせ!
    Join Date
    Oct 2007
    Location
    Inside my computer
    Posts
    24,654
    It wouldn't destroy them per se, but it can certainly mess things up.
    Take boost's shared_ptr, for example. It uses a reference count, yes? That means every time the copy constructor is called, it adds one. But if you copy it using memcpy, then it won't update the reference count.
    Another example might be when an object does a deep copy in the copy constructor.
    So realloc would screw up both of those type of objects. That's why it would be dangerous.
    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.

  4. #19
    Algorithm Dissector iMalc's Avatar
    Join Date
    Dec 2005
    Location
    New Zealand
    Posts
    6,318
    Quote Originally Posted by cpjust View Post
    Yeah, I guess it couldn't use realloc() & placement new (I got the name wrong before) by default since some objects might rely on their own memory address internally... But maybe a custom allocator that uses realloc() & placement new would work if you know how the objects stored in it will handle a change of address behind the scenes.
    Even that turns out not to work. The vector is written to acquire the new lot of memory, then copy stuff across, and then release the old bit of memory. An allocator using realloc wouldn't know which bit of memory to resize, and even if it did, the vector would still try and copy from itself to itself and then destruct the original items which would actually destroy the new ones instead, screwing it up entirely.

    Regardless, if you could use realloc with a vector, it wouldn't make any useful speed improvement anyway.
    Last edited by iMalc; 01-27-2008 at 02:39 PM.
    My homepage
    Advice: Take only as directed - If symptoms persist, please see your debugger

    Linus Torvalds: "But it clearly is the only right way. The fact that everybody else does it some other way only means that they are wrong"

  5. #20
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    It's true that the realloc semantics are simply not safe for C++. However, a slightly different function would be safe. This function, let's call it exalloc, has the following semantics:

    bool exalloc(void *current, size_t newsize);

    Tries to extend the allocated block at current to newsize. If it's not possible to extend the block, it does nothing.

    Arguments:
    current: A pointer previously returned by malloc() or calloc().
    newsize: The desired size, in bytes, to extend the block to.

    Returns:
    True if the block could be extended. The block at current now points to newsize valid bytes. False if the block couldn't be extended. Nothing has changed. No memory has been allocated or freed. No memory has been copied.

    Notes:
    This function behaves like the first part of realloc(). However, if it fails to extend the block in-place, it will not allocate a new block and copy the memory there. This makes it safe for memory containing C++ objects.



    With this function, a dynamic array's push_back could look like this (NOT exception-safe!):
    Code:
    void push_back(const E &e)
    {
      if(m_last == m_memend) {
        size_t newalloc = (m_memend - m_first) * 2;
        if(!exalloc(m_first, newalloc)) {
          E *tmp = malloc(newalloc);
          if(!tmp) throw bad_alloc();
          for(E *s = m_first, *t = tmp; s != m_last; ++s, ++t) {
            new (t) E(*s);
            s->~E();
          }
          swap(tmp, m_start);
          m_last = m_start + (m_last - tmp);
          m_memend = m_start + newalloc;
          free(tmp);
        }
      }
      new (m_last) E(e);
      ++m_last;
    }
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  6. #21
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Yes, that's exactly what I'd like to use instead of realloc()! But how exactly would you implement exalloc()? Is there a portable way to do it, or would you have to rely on OS API calls?

  7. #22
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    There's absolutely no way to implement it aside from implementing the entire malloc family yourself. It needs internal knowledge of the data structures malloc uses.

    You could take an open-source malloc implementation and adapt it.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  8. #23
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by CornedBee View Post
    There's absolutely no way to implement it aside from implementing the entire malloc family yourself. It needs internal knowledge of the data structures malloc uses.
    I think you're right.
    I love a challenge, so I tried to implement my own C++ Realloc() (for Windows), but alas I failed... It seemed to be going pretty well until I tried deleting the old memory, since there's no Placement Delete, and no generic way of calling a destructor in a template.
    Here's my attempt:
    Code:
    template <typename T>
    T* Realloc( T* ptr, size_t oldSize, size_t newSize )
    {
    	T* pNew = (T*)HeapReAlloc( GetProcessHeap(),
    				   HEAP_REALLOC_IN_PLACE_ONLY,
    				   ptr,
    				   newSize );
    	if ( pNew == NULL )
    	{
    		cout << "pNew is NULL!" << endl;
    		pNew = (T*)HeapAlloc( GetProcessHeap(), 0, newSize );
    
    		if ( pNew == NULL )
    		{
    			throw std::bad_alloc( "No memory!" );
    		}
    
    		for ( size_t i = 0; i < oldSize; ++i )
    		{
    			new( &pNew[i] ) T( ptr[i] );	// Copy old value to new location.
    			delete &ptr[i];			// Destroy old value ???  No Placement delete?
    		}
    
    		HeapFree( GetProcessHeap(), 0, ptr );
    	}
    	else if ( pNew == ptr )
    	{
    		cout << "pNew == ptr." << endl;
    	}
    	else
    	{
    		cout << "pNew != ptr!" << endl;
    	}
    
    	return pNew;
    }

  9. #24
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    t.~T() ought to work for explicit destruction. And I didn't know HeapRealloc had the IN_PLACE_ONLY option.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  10. #25
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by CornedBee View Post
    t.~T() ought to work for explicit destruction. And I didn't know HeapRealloc had the IN_PLACE_ONLY option.
    That's what I tried the first time, but the compiler didn't know what I was talking about.
    I think it's literally looking for a destructor named "~T()" if you do that.

  11. #26
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    What compiler do you use? What's your exact code? Because that's exactly the syntax used in The C++ Programming Language. (Well, it uses indirect member access, of course.)
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  12. #27
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    Quote Originally Posted by CornedBee View Post
    What compiler do you use? What's your exact code? Because that's exactly the syntax used in The C++ Programming Language. (Well, it uses indirect member access, of course.)
    I'll try it again when I get home, but it was something like:
    Code:
    ptr[i].~T();
    But T is a typename, so how would the compiler know what ~T() means? Or does it just expand the name like some kind of macro and then figure out if what it expanded to makes sense?

  13. #28
    Cat without Hat CornedBee's Avatar
    Join Date
    Apr 2003
    Posts
    8,895
    A type identifier (even a template parameter) prefixed by the tilde means the destructor of that object. It's as simple as that.

    The code looks like it ought to work.
    All the buzzt!
    CornedBee

    "There is not now, nor has there ever been, nor will there ever be, any programming language in which it is the least bit difficult to write bad code."
    - Flon's Law

  14. #29
    and the hat of sweating
    Join Date
    Aug 2007
    Location
    Toronto, ON
    Posts
    3,545
    You're right, ~T() does work.
    I think before I was doing: ptr[i]->~T();

    Now it seems to work fine.
    I beefed up the code to act like new if ptr is NULL, and act like delete if newSize is 0.
    Code:
    template <typename T>
    T* Realloc( T*  ptr,
    	size_t  oldSize,
    	size_t  newSize )
    {
    	if ( oldSize == newSize )
    	{
    		return ptr;
    	}
    
    	HANDLE hHeap = GetProcessHeap();
    	T* pNew = NULL;
    
    	if ( ptr != NULL )	// If no memory yet, go straight to HeapAlloc().
    	{
    		// Try to expand array.
    		pNew = (T*)HeapReAlloc( hHeap,
    					HEAP_REALLOC_IN_PLACE_ONLY,
    					ptr,
    					(newSize * sizeof( T )) );
    	}
    
    	if ( pNew == NULL )	// Failed to expand array.
    	{
    		if ( newSize > 0 )
    		{
    			// Try to allocate new memory and copy old array to new one.
    			pNew = (T*)HeapAlloc( hHeap, 0, (newSize * sizeof( T )) );
    
    			if ( pNew == NULL )	// Not enough memory.
    			{
    				throw std::bad_alloc( "No memory!" );
    			}
    		}
    
    		// Copy all old values to new array and call each old elements destructor.
    		for ( size_t i = 0; i < oldSize; ++i )
    		{
    			if ( i < newSize )	// In case user wants to delete or shrink array.
    			{
    				new( &pNew[i] ) T( ptr[i] );
    			}
    			ptr[i].~T();
    		}
    
    		if ( ptr != NULL )
    		{
    			HeapFree( hHeap, 0, ptr );	// Release old memory.
    		}
    	}
    
    	// Fill the rest of the array with default T's.
    	for ( size_t i = oldSize; i < newSize; ++i )
    	{
    		new( &pNew[i] ) T();
    	}
    
    	return pNew;
    }

  15. #30
    Registered User
    Join Date
    Apr 2006
    Posts
    2,149
    What if ptr is null, but oldSize is non-zero? You're code does not seem to handle that case well.
    It is too clear and so it is hard to see.
    A dunce once searched for fire with a lighted lantern.
    Had he known what fire was,
    He could have cooked his rice much sooner.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 16
    Last Post: 05-29-2009, 07:25 PM
  2. Have problems with copying my array!
    By AvaGodess in forum C Programming
    Replies: 11
    Last Post: 09-25-2008, 12:56 AM
  3. Replies: 6
    Last Post: 11-09-2006, 03:28 AM
  4. Unknown Memory Leak in Init() Function
    By CodeHacker in forum Windows Programming
    Replies: 3
    Last Post: 07-09-2004, 09:54 AM
  5. Help with an Array
    By omalleys in forum C Programming
    Replies: 1
    Last Post: 07-01-2002, 08:31 AM