Object tries to eat potatoeses

Started by
27 comments, last by SiCrane 10 years, 6 months ago


Advertisement

The fourth line creates an unnamed temporary object, assigns it to objs[2], and then the temporary object is destroyed. That's the destructor you're seeing. If this messes up the object being assigned to, then your class does not properly implement its assingment operator to copy the resources.

But also, you're not assigning to a valid object. None of the objects in objs have been constructed, and assignment assumes you're assigning to an already constructed object. You have to properly construct the objects after allocating the memory; if you insist on allocating the memory with malloc, then you can construct the objects in the memory buffer with placement new.



To fix your code, here's how you use placement new


// allocate memory
void* memory = malloc(sizeof(SomeObject)*5);

// cast to SomeObject pointer so array indexing works below
SomeObject *objs = (SomObject*)memory;

// try to create object in 3rd, 2nd and 1st (out of five) memory slots
SomeObject *thirdObj  = new(&objs[2]) SomeObject();
SomeObject *secondObj = new(objs + 1) SomeObject();
SomeObject *firstObj  = new( memory ) SomeObject();

// the return value is just a cast of the allocation:
// thirdObj  == &objs[2] / same pointer
// secondObj == &objs[1] / same pointer
// firstObj  == &objs[0] / same pointer
// firstObj  == (SomeObject*)memory / same pointer

// objs[0], objs[1] and objs[2] are now valid C++ objects, which you have to destruct before freeing the allocation
objs[0].~SomeObject();
objs[1].~SomeObject();
objs[2].~SomeObject();
Free(memory);

You should always follow the rule of three when writing C++ code though. At the very least, if your class has a destructor, but you don't want to implement copying/assignment, then you must declare them as being private in order to prevent the compiler from automatically implementing them:


class SomeObject
{
public:
  SomeObject();
  ~SomeObject();
private:
  SomeObject( const SomeObject& );
  SomeObject& operator=( const SomeObject& );
};

Regarding alignment, there's no standard answer -- it depends on the compiler/CPU that you're targeting.
Say that and int variable is 4 bytes, some CPU's will crash if you try to use an int that isn't aligned to an address that's a multiple of 4. An x86 CPU won't crash, but it may run slower if memory isn't aligned correctly.
Usually, for any primitive type, you can think of sizeof(Type) as being it's required alignment value. For structures, you can use sizeof for it's largest member. e.g. a struct with a 64-bit (8byte) variable and a 16-bit (2byte) variable should be aligned to an address that's a multiple of 8 bytes.
If you're lazy, you can just align everything to 16 bytes, which will work for 99.9% of cases on PC tongue.png
On MSVC, you can use _aligned_malloc/_aligned_free instead of malloc/free to get aligned allocations.

But yes, you should feel pretty uneasy when using placement new and aligning your own memory -- these tasks should generally only be performed deep inside low-level container classes like std::vector.

From your other thread, it sounds like what you're looking for is a pool container. I have an open source one (header, implementation), but it's not exactly documented or readable tongue.png

If you're still learning C++, I'd really recommend just using a simple/straightforward solution that you're more comfortable with for now. KISS is always a useful principle to follow in order to keep out bugs and get things done.



With your example of having a stuct with two variables 8B and 2B , you suggest aligning it to 8B, I would assume the structure would need 16B then ? How would that also work out if sizeof(exampleStruct) return 10B then ? would each member require a minimum of 8B if it's actual size is below?

When you create an array of structures, they're spaced sizeof(structure) apart. If the struct requires 8B alignment (because it has an 8B member), but the size is 10B, then the first element will be aligned correctly, but the 2nd/3rd/etc elements in the array won't be aligned correctly.
To simplify this, the compiler will insert padding into structures, making them bigger than they need to be.

e.g. Try running this code:


struct Example { long long eightBytes; short twoBytes; };
printf( "%d", sizeof(Example) );

It should print 16, not 10! The compiler inserts 6 "padding" bytes after the last member, to ensure that the size of the struct is alignable. The first member requires an alignment of 8, the second member requires an alignment of 2.
As long as the start of the array is aligned to a multiple of 8 (or 16, or 32...), then every element in the array will also be correctly aligned, and every member will be correctly aligned.
e.g. say the array starts at address #48 -- array[0].eightBytes is at #48, a multiple of 8, and array[0].twoBytes is at #56, a multiple of 2. array[1].eightBytes is at #64, a multiple of 8, and so on.

Also how would you actually do the aligning ?

It used to be fairly complicated, so I would've suggested just using an alignment of 16 for everything. Since C++11 though, as well as sizeof(T), we also now have alignof(T).

e.g. for MS Visual Studio, you can use the aligned malloc/free functions like this:


template<class T>
T* AllocArray( int count )
{
	return (T*)_aligned_malloc( sizeof(T)*count, alignof(T) );
}
void FreeArray( void* ptr )
{
	_aligned_free( ptr );
}


Example* myArray = AllocArray<Example>( 5 );

new(&myArray[2]) Example();

myArray[2].~Example();

FreeArray( myArray );

[edit]Instead of using MSVC-specific functions, the portable C++11 version of the above is:


template<class T>
T* AllocArray( int count )
{
	return (T*)aligned_alloc( alignof(T), sizeof(T)*count );
}
void FreeArray( void* ptr )
{
	free( ptr );
}

"malloc" aligns by default with 8 bytes or something similar, any possible confirmations ?

Yes, (IIRC...) this is true -- malloc will always return an allocation that is correctly aligned for any standard primitive type, which in practice, means it's aligned to 8 bytes.

However, there are also non-standard primitive types, such as __m128 which is an SSE variable representing 4 floats stored together, and has a size/alignment of 16-bytes. That's why I recommend using 16 bytes as the "default" alignment value, not 8 bytes as the standard recommends... wacko.png

The above example of alignof(T) always selects the correct alignment though, so you don't have to worry about picking a good default one-size-fits-all

You should always follow the rule of three when writing C++ code though. At the very least, if your class has a destructor, but you don't want to implement copying/assignment, then you must declare them as being private in order to prevent the compiler from automatically implementing them

Or, in C++11, you'd explicitly 'delete' them; and if you want to show others that you really desire default-generated functions, then you can explicitly mark them as 'default'.


class SomeObjectInCpp11
{
public:
  SomeObject() = default; //Have the compiler generate it.
  SomeObject( const SomeObject& ) = delete; //Make sure others can't use it.
  SomeObject& operator=( const SomeObject& ) = delete;
  ~SomeObject(); //Define it yourself
  
  //For objects managing data, these are also useful to implement
  //(usually as a swap of the data pointers, rather than copying the data).
  SomeObject( SomeObject&& ); //Move-constructor
  SomeObject& operator=(SomeObject&&); //Move-assignment operator
};

People joke that in C++11 the 'rule of three' is now the 'rule of five'. While not a strict requirement, the two additional functions can speed up the code using the class by a great deal, and is usually easy to implement.

Rule of Three in C++ - Wikipedia

Wow.

The first thing you should do is, forget that malloc exists as long as you want to program in C++ and dont try to work around doing it wrong by hacking around it with wrong C-typecasts or placement new. Operator new and delete got introduced into C++ precisely to avoid errors with not calling constructors and destructors.

Just do it like this and have an automagically stack-allocated array of 2 readily constructed SomeObject objects:


SomeObject objects[2];

Or like this to get a resizable vector:


// at beginning of file
#include <vector>

// later when you need it
std::vector<SomeObject> objects;
objects.resize(2); // or add objects in some other way, for example with push_back

Or if you can't resist doing it in an inefficient, unsafe, contrived way and want to get memory leaks if you forget something:


SomeObject *objects=nullptr;
try {
  objects=new SomeObject[2];
  // do whatever you need here
  delete[] objects;
} catch(...) {
  delete[] objects;
  throw;
}

^ This. I'm not seeing yet why you're specifically avoiding 'new' (unless you're doing it for learning purposes, in which case do what you will :D ).

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator



This topic is closed to new replies.

Advertisement