Dynamic memory management, straightening my understanding of itsdetails

F

Francesco S. Carta

Hi there,
I'm trying to understand the details of the dynamic memory management.

These are the various sections I've read these days:

1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]

The last sections above are quite technical and wide, I'm still far from
having any decent grasp on them.

Before going on I need to clear out if what I understood so far is correct.

I wrote the following at the best of my comprehension and, as far as I
can tell, it should be a well-defined C++ program, please tell me if and
where I've gone off the track:

//-------

#include <iostream>
#include <new>

using namespace std;

const int n = 2;

void int_test() {

cout << " # int_test()" << endl;

// allocate
int* ptr = static_cast<int*>(
operator new(sizeof(int))
);

// non-initialized creation
new(ptr) int;

// prints garbage
cout << *ptr << endl;

// zero-initialized creation
new(ptr) int();

// prints zero
cout << *ptr << endl;

// deallocate
operator delete(ptr);

cout << endl << " # int_test(), array version" << endl;

// allocate
int* arr_ptr = static_cast<int*>(
operator new[](n * sizeof(int))
);

// non-initialized creation
new(arr_ptr) int[n];

// prints garbage values
for (int i = 0; i < n; ++i) {
cout << arr_ptr << endl;
}

// zero-initialized creation
new(arr_ptr) int[n]();

// prints zeros
for (int i = 0; i < n; ++i) {
cout << arr_ptr << endl;
}

// deallocate
operator delete[](arr_ptr);
}

struct POD {
int data;
};

void POD_test() {

cout << " # POD_test()" << endl;

// allocate
POD* ptr = static_cast<POD*>(
operator new(sizeof(POD))
);

// non-initialized creation
new(ptr) POD;
// prints garbage
cout << ptr->data << endl;

// zero-initialized creation
new(ptr) POD();
// prints zero
cout << ptr->data << endl;

// deallocate
operator delete(ptr);

cout << endl << " # POD_test(), array version" << endl;

// allocate
POD* arr_ptr = static_cast<POD*>(
operator new[](n * sizeof(POD))
);

// non-initialized creation
new(arr_ptr) POD[n];

// prints garbage values
for (int i = 0; i < n; ++i) {
cout << arr_ptr.data << endl;
}

// zero-initialized creation
new(arr_ptr) POD[n]();

// prints zeros
for (int i = 0; i < n; ++i) {
cout << arr_ptr.data << endl;
}

// deallocate
operator delete[](arr_ptr);
}

struct Base {
virtual int data() const = 0;
virtual ~Base() {}
};

class Derived : public Base {
public:
Derived(int i = 123) : ptr(new int(i)) {
cout << "Derived::Derived(int)" << endl;
}
Derived(const Derived& d) : ptr(new int(*d.ptr)) {
cout << "Derived::Derived(const Derived&)" << endl;
}
Derived& operator=(const Derived& d) {
cout << "Derived::eek:perator=(const Derived&)" << endl;
*ptr = *d.ptr;
return *this;
}
~Derived() {
cout << "Derived::~Derived()" << endl;
delete ptr;
}
int data() const {
return *ptr;
}
private:
int* ptr;
};


void Derived_test() {

cout << " # Derived_test()" << endl;

// allocate
Derived* ptr = static_cast<Derived*>(
operator new(sizeof(Derived))
);

// according to 12.1p8 [class.ctor]
// this calls Derived::Derived(123)
new(ptr) Derived;

// prints 123
cout << ptr->data() << endl;

// call destructor
ptr->~Derived();

// calls Derived::Derived(321)
new(ptr) Derived(321);

// prints 321
cout << ptr->data() << endl;

// call destructor
ptr->~Derived();

// deallocate
operator delete(ptr);

cout << endl << " # Derived_test(), array version" << endl;

// allocate
Derived* arr_ptr = static_cast<Derived*>(
operator new[](n * sizeof(Derived))
);

// calls Derived::Derived(123) n-times
new(arr_ptr) Derived[n];

// prints 123 n-times
for (int i = 0; i < n; ++i) {
cout << arr_ptr.data() << endl;
}

// call destructors
for (int i = n; i > 0; --i) {
arr_ptr[i-1].~Derived();
}

// calls Derived::Derived(123) n-times
new(arr_ptr) Derived[n]();

// prints 123 n-times
for (int i = 0; i < n; ++i) {
cout << arr_ptr.data() << endl;
}

// call destructors
for (int i = n; i > 0; --i) {
arr_ptr[i-1].~Derived();
}

// deallocate
operator delete[](arr_ptr);
}

int main() {

int_test();

cout << "===========" << endl;

POD_test();

cout << "===========" << endl;

Derived_test();

return 0;
}

//-------

Thank you for your attention.
 
B

Bo Persson

Francesco said:
Hi there,
I'm trying to understand the details of the dynamic memory
management.
These are the various sections I've read these days:

1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]

The last sections above are quite technical and wide, I'm still far
from having any decent grasp on them.

Before going on I need to clear out if what I understood so far is
correct.
I wrote the following at the best of my comprehension and, as far
as I can tell, it should be a well-defined C++ program, please tell
me
if and where I've gone off the track:

//-------

Other than that printing unitialized data is undefined behavior, it
seems pretty much ok.

And that you must promise to never, ever do this in a real program!


Bo Persson
 
F

Francesco S. Carta

Francesco said:
Hi there,
I'm trying to understand the details of the dynamic memory
management.
These are the various sections I've read these days:

1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]

The last sections above are quite technical and wide, I'm still far
from having any decent grasp on them.

Before going on I need to clear out if what I understood so far is
correct.
I wrote the following at the best of my comprehension and, as far
as I can tell, it should be a well-defined C++ program, please tell
me
if and where I've gone off the track:

//-------

Other than that printing unitialized data is undefined behavior, it
seems pretty much ok.

[dcl.init] 8.5p9 seems to tell me that an uninitialized data like that
"int" in my code has just an indeterminate initial value, I don't
recognize that as UB - printing it out should be well-defined: it should
always show that initial value, it shouldn't shoot me in the foot ;-)

Though, maybe, you were referring to some other clause - could you
please point it out for me, if that's the case?
And that you must promise to never, ever do this in a real program!

Sorry, I cannot :)

I want to use the raw storage allocation in a home-cooked Vector
template - in it.comp.lang.c++ a person was looking for a tutorial about
a dynamic menu to be used in an embedded C++ environment.

I've presented a half-cooked example using std::vector, but as it turned
out, they don't have std::vector available (among other missing parts of
the STL). So far, my Vector template (still incomplete of course) seems
to work fine in my setup - for the functionalities needed to implement
the menu.

Of course, such a low level memory handling would be relegated to
well-defined internal functions (the purpose is to create a minimal,
self-contained Vector template, in other words).

Thank you for reviewing my code, Bo. Now I can continue studying the
standard and get a grip on more advanced details.
 
A

Alf P. Steinbach /Usenet

* Francesco S. Carta, on 30.08.2010 19:59:
Francesco said:
Hi there,
I'm trying to understand the details of the dynamic memory
management.
These are the various sections I've read these days:

1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]

The last sections above are quite technical and wide, I'm still far
from having any decent grasp on them.

Before going on I need to clear out if what I understood so far is
correct.
I wrote the following at the best of my comprehension and, as far
as I can tell, it should be a well-defined C++ program, please tell
me
if and where I've gone off the track:

//-------

Other than that printing unitialized data is undefined behavior, it
seems pretty much ok.

[dcl.init] 8.5p9 seems to tell me that an uninitialized data like that "int" in
my code has just an indeterminate initial value, I don't recognize that as UB -
printing it out should be well-defined: it should always show that initial
value, it shouldn't shoot me in the foot ;-)

Though, maybe, you were referring to some other clause - could you please point
it out for me, if that's the case?

In practice it's just arbitrary values.

But formally, except that char, unsigned char and signed char have only value
representation bits, the standard allows for non-participating bits that may be
used for e.g. error checking, literally trapping your program. This is early in
the standard somewhere, section 3 perhaps? Even so, on esoteric machine, you
should be safe for integers, but floating point values are different.

Sorry, I cannot :)

I want to use the raw storage allocation in a home-cooked Vector template - in
it.comp.lang.c++ a person was looking for a tutorial about a dynamic menu to be
used in an embedded C++ environment.

Why, do you think a new expression is somehow inefficient or needs extra runtime
lib support or something?

It's efficient and it does what you're doing manually, only more safe.

You can always use std::nothrow (from header [new]) to avoid those pesky
exceptions...

I've presented a half-cooked example using std::vector, but as it turned out,
they don't have std::vector available (among other missing parts of the STL). So
far, my Vector template (still incomplete of course) seems to work fine in my
setup - for the functionalities needed to implement the menu.

It may be that in an embedded environment they don't support templates either...

I'd check out STLPort if I were you.

I think it supports almost any environment.

Of course, such a low level memory handling would be relegated to well-defined
internal functions (the purpose is to create a minimal, self-contained Vector
template, in other words).

Thank you for reviewing my code, Bo. Now I can continue studying the standard
and get a grip on more advanced details.


Cheers & hth.,

- Alf
 
F

Francesco S. Carta

on said:
* Francesco S. Carta, on 30.08.2010 19:59:
Francesco S. Carta wrote:
Hi there,
I'm trying to understand the details of the dynamic memory
management.
These are the various sections I've read these days:

1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]

The last sections above are quite technical and wide, I'm still far
from having any decent grasp on them.

Before going on I need to clear out if what I understood so far is
correct.
I wrote the following at the best of my comprehension and, as far
as I can tell, it should be a well-defined C++ program, please tell
me
if and where I've gone off the track:

//-------

Other than that printing unitialized data is undefined behavior, it
seems pretty much ok.

[dcl.init] 8.5p9 seems to tell me that an uninitialized data like that
"int" in
my code has just an indeterminate initial value, I don't recognize
that as UB -
printing it out should be well-defined: it should always show that
initial
value, it shouldn't shoot me in the foot ;-)

Though, maybe, you were referring to some other clause - could you
please point
it out for me, if that's the case?

In practice it's just arbitrary values.

But formally, except that char, unsigned char and signed char have only
value representation bits, the standard allows for non-participating
bits that may be used for e.g. error checking, literally trapping your
program. This is early in the standard somewhere, section 3 perhaps?
Even so, on esoteric machine, you should be safe for integers, but
floating point values are different.

In understand, thank you for pointing it out. Of course I would never
use uninitialized data in real code, but I actually thought it was safe,
that's why I left it in the code I posted for revision.

The next time I'll post test code like that I'll make sure to omit it or
to properly mark it as unsafe code.
Why, do you think a new expression is somehow inefficient or needs extra
runtime lib support or something?

At all, I'm sure the implementation can and very likely will do better
than me, only that in this case I want to do something simpler (as I
look at it, at least).
It's efficient and it does what you're doing manually, only more safe.

You can always use std::nothrow (from header [new]) to avoid those pesky
exceptions...

The purpose is, among other things, to simplify my code. Up to some days
ago, my code looked like this:

value_type* allocate(size_t n) {
return
reinterpret_cast<value_type*>(
new char[n * sizeof(value_type)]
};
}

void deallocate(void* ptr) {
delete[] reinterpret_cast<char*>(ptr);
}

Now I can write it so:

value_type* allocate(size_t n) {
return
static_cast<value_type*>(
operator new[] (n * sizeof(value_type))
};
}

void deallocate(void* ptr) {
operator delete[](ptr);
}

(both functions are private static members of my Vector template, modulo
typos as I typed them here in the newsreader)

I know it doesn't change much between the two versions, but I wanted to
dig the issue and simplify the code because I've been told I could
directly call the operator new/delete functions.

I actually like it more implemented in this way, but if there are
caveats or dangers to watch out for, I'll gladly listen to more
experienced ones' suggestions.
It may be that in an embedded environment they don't support templates
either...

I've asked the OP (in i.c.l.c++) in order to ascertain that. I just took
the chance to dig something that wasn't into my chords (all this stuff
about dynamic memory) and to learn about the standard interface
requirements and about the MinGW implementation of std::vector (I've
tried to follow it as much as I could, but I omitted to implement it
using allocators because I'm not that skilled yet, I need more study for
that).
I'd check out STLPort if I were you.

I think it supports almost any environment.

Ah, thank you very much for your pointer, I'll look it up and I'll tell
the OP about this.
 
M

mingze zhang

Hi there,
I'm trying to understand the details of the dynamic memory management.

These are the various sections I've read these days:

1.7     [intro.memory]
3.7.3   [basic.stc.dynamic]
5.3.4   [expr.new]
5.3.5   [expr.delete]
12.5    [class.free]
18.4.1  [lib.new.delete]
20.4    [lib.memory]

The last sections above are quite technical and wide, I'm still far from
having any decent grasp on them.

Before going on I need to clear out if what I understood so far is correct.

I wrote the following at the best of my comprehension and, as far as I
can tell, it should be a well-defined C++ program, please tell me if and
where I've gone off the track:

//-------

#include <iostream>
#include <new>

using namespace std;

const int n = 2;

void int_test() {

     cout << " # int_test()" << endl;

     // allocate
     int* ptr = static_cast<int*>(
                    operator new(sizeof(int))
                );

     // non-initialized creation
     new(ptr) int;

     // prints garbage
     cout << *ptr << endl;

     // zero-initialized creation
     new(ptr) int();

     // prints zero
     cout << *ptr << endl;

     // deallocate
     operator delete(ptr);

     cout << endl << " # int_test(), array version" << endl;

     // allocate
     int* arr_ptr = static_cast<int*>(
                        operator new[](n * sizeof(int))
                    );

     // non-initialized creation
     new(arr_ptr) int[n];

     // prints garbage values
     for (int i = 0; i < n; ++i) {
         cout << arr_ptr << endl;
     }

     // zero-initialized creation
     new(arr_ptr) int[n]();

     // prints zeros
     for (int i = 0; i < n; ++i) {
         cout << arr_ptr << endl;
     }

     // deallocate
     operator delete[](arr_ptr);

}

struct POD {
     int data;

};

void POD_test() {

     cout << " # POD_test()" << endl;

     // allocate
     POD* ptr = static_cast<POD*>(
                    operator new(sizeof(POD))
                );

     // non-initialized creation
     new(ptr) POD;
     // prints garbage
     cout << ptr->data << endl;

     // zero-initialized creation
     new(ptr) POD();
     // prints zero
     cout << ptr->data << endl;

     // deallocate
     operator delete(ptr);

     cout << endl << " # POD_test(), array version" << endl;

     // allocate
     POD* arr_ptr = static_cast<POD*>(
                        operator new[](n * sizeof(POD))
                    );

     // non-initialized creation
     new(arr_ptr) POD[n];

     // prints garbage values
     for (int i = 0; i < n; ++i) {
         cout << arr_ptr.data << endl;
     }

     // zero-initialized creation
     new(arr_ptr) POD[n]();

     // prints zeros
     for (int i = 0; i < n; ++i) {
         cout << arr_ptr.data << endl;
     }

     // deallocate
     operator delete[](arr_ptr);

}

struct Base {
     virtual int data() const = 0;
     virtual ~Base() {}

};

class Derived : public Base {
public:
     Derived(int i = 123) : ptr(new int(i)) {
         cout << "Derived::Derived(int)" << endl;
     }
     Derived(const Derived& d) : ptr(new int(*d.ptr)) {
         cout << "Derived::Derived(const Derived&)" << endl;
     }
     Derived& operator=(const Derived& d) {
         cout << "Derived::eek:perator=(const Derived&)" << endl;
         *ptr = *d.ptr;
         return *this;
     }
     ~Derived() {
         cout << "Derived::~Derived()" << endl;
         delete ptr;
     }
     int data() const {
         return *ptr;
     }
private:
     int* ptr;

};

void Derived_test() {

     cout << " # Derived_test()" << endl;

     // allocate
     Derived* ptr = static_cast<Derived*>(
                        operator new(sizeof(Derived))
                    );

     // according to 12.1p8 [class.ctor]
     // this calls Derived::Derived(123)
     new(ptr) Derived;

     // prints 123
     cout << ptr->data() << endl;

     // call destructor
     ptr->~Derived();

     // calls Derived::Derived(321)
     new(ptr) Derived(321);

     // prints 321
     cout << ptr->data() << endl;

     // call destructor
     ptr->~Derived();

     // deallocate
     operator delete(ptr);

     cout << endl << " # Derived_test(), array version" << endl;

     // allocate
    Derived* arr_ptr = static_cast<Derived*>(
                            operator new[](n * sizeof(Derived))
                        );

     // calls Derived::Derived(123) n-times
     new(arr_ptr) Derived[n];

     // prints 123 n-times
     for (int i = 0; i < n; ++i) {
         cout << arr_ptr.data() << endl;
     }

     // call destructors
     for (int i = n; i > 0; --i) {
         arr_ptr[i-1].~Derived();
     }

     // calls Derived::Derived(123) n-times
     new(arr_ptr) Derived[n]();

     // prints 123 n-times
     for (int i = 0; i < n; ++i) {
         cout << arr_ptr.data() << endl;
     }

     // call destructors
     for (int i = n; i > 0; --i) {
         arr_ptr[i-1].~Derived();
     }

     // deallocate
     operator delete[](arr_ptr);

}

int main() {

     int_test();

     cout << "===========" << endl;

     POD_test();

     cout << "===========" << endl;

     Derived_test();

     return 0;

}

//-------

Thank you for your attention.


Hi,

running the above code in Vc++2008 causes run-time-error.

Further checking on this, the placement new[] on classes with virtual
functions may cause problem for different compilers (GCC seems works
fine). I didn't really check the standard but the following thread may
give you hints.

http://stackoverflow.com/questions/15254/can-placement-new-for-arrays-be-used-in-a-portable-way
 
L

Luc Danton

Hi there,
I'm trying to understand the details of the dynamic memory management.
[...]
// allocate
int* ptr = static_cast<int*>(
operator new(sizeof(int))
);

// non-initialized creation
new(ptr) int;
[...]

As a matter of style, I prefer to separate the storage from the object;
so I'd write that snippet that way:

// For any type T
// allocate
void* storage = ::eek:perator new(sizeof(T));

// construct
T* ptr = new (storage) T;
//destroy
ptr->~T();

// deallocate
::eek:perator delete(storage);

Alternatively, storage could be an unsigned char*, which would require a
static_cast. Note that allocating storage with your style could be:

T* ptr = static_cast<T*>:):eek:perator new(sizeof *ptr));

which has one less maintenance point (i.e. if you change T, you don't
have to change the argument to operator new). With the former style,
hopefully T is a typedef or a template parameter (but then you have to
consider the cases where *ptr is uninitialized, or default to zero-init).
 
F

Francesco S. Carta

Hi there,
I'm trying to understand the details of the dynamic memory management.
[...]
// allocate
int* ptr = static_cast<int*>(
operator new(sizeof(int))
);

// non-initialized creation
new(ptr) int;
[...]

As a matter of style, I prefer to separate the storage from the object;
so I'd write that snippet that way:

// For any type T
// allocate
void* storage = ::eek:perator new(sizeof(T));

// construct
T* ptr = new (storage) T;
//destroy
ptr->~T();

// deallocate
::eek:perator delete(storage);

Alternatively, storage could be an unsigned char*, which would require a
static_cast. Note that allocating storage with your style could be:

T* ptr = static_cast<T*>:):eek:perator new(sizeof *ptr));

which has one less maintenance point (i.e. if you change T, you don't
have to change the argument to operator new). With the former style,
hopefully T is a typedef or a template parameter (but then you have to
consider the cases where *ptr is uninitialized, or default to zero-init).

Thank you for your post, actually, all this studying is aimed to
understanding some specific behaviors, and all the code (along with its
comments) is just a way check that understanding.

Actually, I need to store the raw uninitialized memory directly as a
pointer to the type whose instances will be created there via placement
new, as I'm implementing the internals of a Vector template.
 
F

Francesco S. Carta

Hi there,
I'm trying to understand the details of the dynamic memory management.

These are the various sections I've read these days:

1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]

The last sections above are quite technical and wide, I'm still far from
having any decent grasp on them.

Before going on I need to clear out if what I understood so far is correct.

I wrote the following at the best of my comprehension and, as far as I
can tell, it should be a well-defined C++ program, please tell me if and
where I've gone off the track:
Thank you for your attention.

Hi,

running the above code in Vc++2008 causes run-time-error.

Further checking on this, the placement new[] on classes with virtual
functions may cause problem for different compilers (GCC seems works
fine). I didn't really check the standard but the following thread may
give you hints.

http://stackoverflow.com/questions/15254/can-placement-new-for-arrays-be-used-in-a-portable-way

Thank you for the notice and for the reference, luckily in my case it's
not a big issue as in the code I'm creating I will not be creating
objects with "placement new[]" but just with "placement new" (this was
just a test for my understanding, and now I'm also aware of another
danger to watch out for, thanks to your post).
 
F

Francesco S. Carta

Hi there,
I'm trying to understand the details of the dynamic memory
management. [...]
cout<< endl<< " # Derived_test(), array version"<< endl;

// allocate
Derived* arr_ptr = static_cast<Derived*>(
operator new[](n * sizeof(Derived))
);

// calls Derived::Derived(123) n-times
new(arr_ptr) Derived[n];
[...]

running the above code in Vc++2008 causes run-time-error.

Further checking on this, the placement new[] on classes with virtual
functions may cause problem for different compilers (GCC seems works
fine). I didn't really check the standard but the following thread may
give you hints.

http://stackoverflow.com/questions/15254/can-placement-new-for-arrays-b
e-used-in-a-portable-way

Yes, the placement new[] may want to store the object count somewhere in
order to know how many objects to delete with delete[]. The amount of
additional storage and when exactly it is needed is compiler-specific.

In practice I think one can get away with allocating std::max(sizeof
(size_t), alignment_of<Derived>()) more bytes; alignment_of<> is left as
exercise to the reader. Here Derived does not contain larger members than
pointers, so just sizeof(size_t) should work with flat address space
model:

Derived* arr_ptr_raw = static_cast<Derived*>(
operator new[](n * sizeof(Derived) +
sizeof(size_t))
);
Derived* arr_ptr = new(arr_ptr_raw) Derived[n];

One can verify afterwards that the difference of the pointers is not more
than the extra allocated space.

Of course, this is all underspecified in the standard and highly
unportable and thus should be better avoided.

Thank you for the info and for the technique to use in order to work
around the issue - I'll be good without placement new[], for the moment,
but I will dig the issue to ensure that my Vector class will be able to
cope with this.
 
J

James Kanze

* Francesco S. Carta, on 30.08.2010 19:59:
Francesco S. Carta wrote:
Hi there,
I'm trying to understand the details of the dynamic memory
management.
These are the various sections I've read these days:
1.7 [intro.memory]
3.7.3 [basic.stc.dynamic]
5.3.4 [expr.new]
5.3.5 [expr.delete]
12.5 [class.free]
18.4.1 [lib.new.delete]
20.4 [lib.memory]
The last sections above are quite technical and wide, I'm
still far from having any decent grasp on them.
Before going on I need to clear out if what I understood
so far is correct.
I wrote the following at the best of my comprehension and,
as far as I can tell, it should be a well-defined C++
program, please tell me if and where I've gone off the
track:
//-------
Other than that printing unitialized data is undefined behavior, it
seems pretty much ok.
[dcl.init] 8.5p9 seems to tell me that an uninitialized data
like that "int" in my code has just an indeterminate initial
value, I don't recognize that as UB - printing it out should
be well-defined: it should always show that initial value,
it shouldn't shoot me in the foot ;-)
Though, maybe, you were referring to some other clause -
could you please point it out for me, if that's the case?
In practice it's just arbitrary values.
But formally, except that char, unsigned char and signed char
have only value representation bits, the standard allows for
non-participating bits that may be used for e.g. error
checking, literally trapping your program. This is early in
the standard somewhere, section 3 perhaps? Even so, on
esoteric machine, you should be safe for integers, but
floating point values are different.

There are, in fact, two ways that it can fail. One is trapping
bits in the hardware. There is at least one machine in
production today which has bits which don't participate in the
value in an integer, and are required to be zero. In this
case, I don't think it will cause a core dump, but the results
could be rather unexpected. (The architecture is tagged, and if
the bits in question aren't zero, it will treat the value as
floating point, or as a pointer.) The other way is simply a
debugging implementation of C++, which somehow tracks which
bytes have been initialized or not. One obvious example of this
is code linked with Purify, although in this case, the
"undefined behavior" is an error message. In the past (and
maybe still---the product still has a Web page saying you can
buy it), CenterLine C++ did such error checking as well.
Why, do you think a new expression is somehow inefficient or
needs extra runtime lib support or something?
It's efficient and it does what you're doing manually, only more safe.

It depends. Within containers, it is often useful to separate
allocation and initialization.
 
J

James Kanze

I'm trying to understand the details of the dynamic memory
management. [...]
cout<< endl<< " # Derived_test(), array version"<< endl;
// allocate
Derived* arr_ptr = static_cast<Derived*>(
operator new[](n * sizeof(Derived))
);
// calls Derived::Derived(123) n-times
new(arr_ptr) Derived[n]; [...]
running the above code in Vc++2008 causes run-time-error.
Further checking on this, the placement new[] on classes with virtual
functions may cause problem for different compilers (GCC seems works
fine).

Only if you're lucky (or unlucky, depending on how you view the
issue).
I didn't really check the standard but the following thread may
give you hints.
http://stackoverflow.com/questions/15254/can-placement-new-for-arrays-b
e-used-in-a-portable-way
Yes, the placement new[] may want to store the object count
somewhere in order to know how many objects to delete with
delete[]. The amount of additional storage and when exactly
it is needed is compiler-specific.
In practice I think one can get away with allocating
std::max(sizeof (size_t), alignment_of<Derived>()) more
bytes; alignment_of<> is left as exercise to the reader.
Here Derived does not contain larger members than pointers,
so just sizeof(size_t) should work with flat address space
model:
Derived* arr_ptr_raw = static_cast<Derived*>(
operator new[](n * sizeof(Derived) +
sizeof(size_t))
);
Derived* arr_ptr = new(arr_ptr_raw) Derived[n];
One can verify afterwards that the difference of the
pointers is not more than the extra allocated space.
Of course, this is all underspecified in the standard and
highly unportable and thus should be better avoided.
Thank you for the info and for the technique to use in order
to work around the issue - I'll be good without placement
new[], for the moment, but I will dig the issue to ensure that
my Vector class will be able to cope with this.

In general, never use the array form of placement new. If
you're using placement new, and you're building an array, loop
over the values using placement new for each individual element.
(In all of the actual uses of placement new I know, this is what
you'd want to do anyway.)
 
F

Francesco S. Carta

[...] I'll be good without placement
new[], for the moment, but I will dig the issue to ensure that
my Vector class will be able to cope with this.

In general, never use the array form of placement new. If
you're using placement new, and you're building an array, loop
over the values using placement new for each individual element.
(In all of the actual uses of placement new I know, this is what
you'd want to do anyway.)

I was just thinking about where and when my Vector template could
eventually need to use placement new[] but I wasn't able to figure it
out. It's good to learn that I don't need to bother about this, thank
you for pointing it out James.
 
F

Francesco S. Carta

[...] I'll be good without placement new[], for the
moment, but I will dig the issue to ensure that my Vector class will
be able to cope with this.

Indeed, a Vector class will need to maintain an explicit element count
anyway, so using array new[] has no benefits whatsoever. Instead, a
Vector class could make use of things like std::uninitialized_fill() and
std::uninitialized_copy(), which contain internal rollback mechanism in
case of exceptions.

Ah, now I see it: I can use uninitialized_fill() when creating a Vector
with an initial size and I can use uninitialized_copy() during
reallocations... very good, I can get rid of a couple of explicit loops,
thank you for pointing them out Paavo.
 
F

Francesco S. Carta

on 30/08/2010 21:11:33 said:
[...] Up to some days
ago, my code looked like this:

value_type* allocate(size_t n) {
return
reinterpret_cast<value_type*>(
new char[n * sizeof(value_type)]
);
}

void deallocate(void* ptr) {
delete[] reinterpret_cast<char*>(ptr);
}

Now I can write it so:

value_type* allocate(size_t n) {
return
static_cast<value_type*>(
operator new[] (n * sizeof(value_type))
);
}

void deallocate(void* ptr) {
operator delete[](ptr);
}

Just wondering... would it make any difference if I call plain "operator
new" and "operator delete" here above, instead of their array counterpart?

Some magic bookkeeping behind them two should be able to correctly
release all the memory regardless of what I've been doing with the
storage between the two calls... is it a correct and reliable assumption?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top