Discussion with my bro a = new Banana; VS a = new Banana()

Coders forum. Discuss techniques, swap code snippets, ask for help etc. relating to all forms of software development.
Post Reply
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Discussion with my bro a = new Banana; VS a = new Banana()

Post by gilesgoat »

Ok as the title says, we are talking C++ ... WHAT is the difference betweeen :

1. a = new Banana;

VS

2. a = new Banana();

I am reading/hearing "all the stories in the world" ..

Who says that '1' "does nothing much", while '2' guarantees all vars are initialised to '0' .. to '1' allocates on the Heap, while '2' allocates on the stack ( and so you mean that if I do that inside a function when the function ends a is super-bogus ?? ) ..

Another thing that I remember hearing was that '1' calls all the constructors while '2' may not call all the constructors ??

I NORMALLY/ONLY use '1' unless is a thing like

3. a = new Banana (parmeters)

Where I do have a specific constructor that says "there MUST be parameters" or it won't work.

Then ok there's '4' too ..

4. Banana a = {};

.. I am confused ...
Giles the billy goat
"Baa"
User avatar
Dio
Sheepie Whore
Sheepie Whore
Posts: 18887
Joined: Mon May 31, 2004 1:08 pm 0
Location: Newcastle UK

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by Dio »

new means it goes on the heap and you have to delete it afterwards.

I don't think the brackets make any difference if there is a parameterless constructor.
User avatar
gilesgoat
Sheepie Whore
Sheepie Whore
Posts: 27258
Joined: Wed Sep 03, 2003 7:54 am 0
Location: Monfalcone, Italy
Contact:

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by gilesgoat »

Dio wrote:new means it goes on the heap and you have to delete it afterwards.

I don't think the brackets make any difference if there is a parameterless constructor.
Now my bro is reading "somewhere else" that according to certain sources "new allocates on heap WHERE THE CPU CAN NOT USE THE CACHE " ?? ..

Does it make any sense ???
Giles the billy goat
"Baa"
User avatar
venusian
Baaaaaaa!
Baaaaaaa!
Posts: 6651
Joined: Wed Nov 06, 2002 2:22 pm 0
Location: South Gloucestershire

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by venusian »

No. That makes no sense at all.

Both the heap and the stack are cached through the d-cache, and in a modern thread-ready world the stack is most likely on the heap anyway.
User avatar
Little stoned kiwi
Baaaaaa!
Baaaaaa!
Posts: 4597
Joined: Mon Oct 27, 2003 6:05 am 0
Location: Christchurch

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by Little stoned kiwi »

I've been told the stack may, *may* be more readily cached as it's often referenced. But probably no different.
The difference comes from the time allocation takes on the stack vs. the heap.
If they think you're crude, go technical; if they think you're technical, go crude. I'm a very technical boy.
User avatar
KlaxonCow
Baaaaaaa!
Baaaaaaa!
Posts: 8444
Joined: Fri Dec 13, 2002 3:46 am 0
Location: A large grassy field.

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by KlaxonCow »

Little stoned kiwi wrote:The difference comes from the time allocation takes on the stack vs. the heap.
Yeah, that's the bugger.

With the stack, you just bump the stack pointer.

You want to allocate, say, 64 bytes? Then just subtract 64 from the stack pointer. Done.

You've created the space on the stack - just use it in the function.

Then, of course, at the end of the function, put the stack pointer back where it was. Done.

So allocation is just a subtract on the stack pointer. One CPU instruction.

Deallocation is either an "add" to put it back... or, often, with the x86, there's the base pointer (BP) register. So just save the stack pointer (SP) into the base pointer (BP), do whatever the hell you like and then put the stack pointer back to where it was by copying that copy you made back out of the base pointer. That's kind of what the base pointer is there to do (to act as a "bookmark" of where you are on the stack). Which is basically a "pop everything I just did off the stack all in one go" instruction. But, again, one CPU instruction.

Whereas, if you're allocating off the heap, then your C library's "::operator new" is making calls to the OS. And then the OS is looking through its tables for enough RAM. It finds some and then is messing with its page tables to mark it as "used" - got to keep track of it all, right? - and then mapping it into the process's address space. And OS memory allocators can be doing all sorts of consolidation and other "admin" to keep things neat and tidy, during that allocation or deallocation.

The heap allocate / deallocate can be costly because, one, you're calling the OS, so there's the user -> kernel -> user transitions. Then the OS might have to do a bunch of "admin" and "book-keeping" to keep track of it all. It depends what allocation is going on inside the OS, but some algorithms could be quite involved, book-keeping-wise, to keep everything neat and tidy.

You get the gist.

Stack allocation is one CPU instruction. Lightning fast. Doesn't have to consult the OS or anything.

Deallocation? Just abandon it. Just put the stack pointer back where it was, and everything's effectively gone (well, of course, it isn't really, it's still in memory but subsequent pushes and pops will just re-use that memory and overwrite it).

And, yes, as the stack is being used all the time - to store return addresses for calls, as well as, often, being used for parameter passing - then it's highly likely that it's already in the cache. When you push and pop even a single byte, it's actually bringing in a whole cache line, remember. So the memory you're about to "allocate" by shifting that stack pointer could well already be resident in the cache, even before you've touched it, as a whole cache line was brought in that just happened to partially, or wholly, include the bytes you want to now allocate.

It's worth noting that, in C++, "new" and "delete" are kind of special. Because, yes, the keywords "new" and "delete" are defined in the language... but they are actually just function calls, found in the standard library that you link with your code, and you can totally override them, if you want:

Code: Select all

void *operator new(size_t size)
{
   return malloc(size);
}

void operator delete(void *ptr)
{
   free(ptr);
}
So "new" and "delete" are actually like "malloc" and "free", in that they are actually calls to library functions, but the difference in C++ is that the language definition insists that they must be provided (and if you're not providing your own, then the standard library you linked to is providing them). But you can totally override them and provide your own (if you're making your own OS, then you have to do that to have "new" and "delete" call your OS's memory management functions - that's basically what the standard library ones, that you link your C++ code with, are doing for you. Or they're doing that by proxy - like the code I posted above, where I'm just calling through to C's "malloc" and "free" under the hood).

Add the above code to a C++ project, compile and then run. Watch as, most likely, absolutely nothing happens. Because, the point is, its functionally equivalent.

(Or, if you want to get fancy, then your "new" and "delete" can call "VirtualAlloc" or "mmap" directly - skip the middle man and deal with the OS yourself. But, as you can tell from the fact that I just named two different functions - the "VirtualAlloc" of Window and the "mmap" of UNIXen - now you're straying into "not portable" territory. That's why, unless you've got good reason, you use the standard library's "new" and "delete", as they already do the work of linking in the right version for the OS you're compiling for, and hide those "non-portable" bits from you.)

There is actually a subtle thing there, though. Because you call "a = new Banana()" but the function call is "void *operator new(size_t size)". C++ does do a little book-keeping for you. It works out what the "sizeof(Banana)" is for you - including all the base classes and derived classes and shit you might be inheriting from. And once it's called "::operator new" to grab the memory for the object, it then calls "Banana::Banana" - the constructor - to initialise that memory.

So there is "operator new", the function call, but C++ is doing a few extra things before and after that call.

(And, of course, "delete" works in reverse. The deconstructor - "Banana::~Banana" is called, then "operator delete" is called with the pointer and is responsible for deallocating it off the heap.)

As you can actually write your own "operator new" functions, I can tell you that "a = new Banana;" and "a = new Banana()" does nothing. I've overridden "new" and "delete" many times, and though I tend to always put "()" for consistency reasons, I'm sure I've left it off sometimes and nothing different happens. Same call sequence, my "malloc" version gets called (and logged in the debug log, so I know that happened) and it's all exactly the same. If it wasn't the case, then my code - overriding "new" - just wouldn't work but it does.

I think "with / without parentheses" is like whether you put "()" or "(void)" in your function declarations. I always put "void" myself - no, you don't have to, because that is C++'s default in the absence of parameters. But I used K&R C back in the day, where the default was actually "int", if you didn't say anything (yeah, I know. Bit mad, eh? That's why they changed it).

In C / C++, unless you do something special like use the "static" keyword or whatever, then local variables will be allocated on the stack. You use "int x;" in the function, this'll generate the "subtract sizeof(int) from stack pointer" instruction, the function can then use this "gap" it just created in the stack to do all its temporary stuff. Then, at function end, they'll probably be a "mov esp, ebp" - move the base pointer into the stack pointer - to reset the stack and we carry on.


p.s. I've talked about subtracting from the stack, but not mentioned that it's subtract, not add, because the stack grows downwards. The general gist of this is that you put the stack at the top of memory and it grows downwards. Then you put the program and data at the start of memory and it grows upwards. So they'll only crash into each other when you've used up the whole address space - and you're going to run out of RAM long before you run out of a 64-bit address space. If you've reached that point then there is no "out" from the situation anyway, as there's no address space left. I mean, actual OSes in real life don't literally put the stack right at the top, nor the programs literally at the very bottom. But that's loosely the general idea. Put them as far apart as possible, they grow in opposite directions. They should, therefore, never meet, unless you've exhausted the entire address space - and if you're at that point, then you're in far bigger trouble anyway.
"Words ought to be a little wild - for they are assaults of thought on the unthinking."
- John Maynard Keynes
User avatar
Dio
Sheepie Whore
Sheepie Whore
Posts: 18887
Joined: Mon May 31, 2004 1:08 pm 0
Location: Newcastle UK

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by Dio »

venusian wrote:Both the heap and the stack are cached through the d-cache, and in a modern thread-ready world the stack is most likely on the heap anyway.
Even discounting pointer allocation, the stack is generally much more memory efficient than the heap, and stacks are not part of the heap, unless you mean in the very most general sense of "They are dynamically allocated memory by the OS" (and even then they are a bit special since they have a bunch of special properties and are allocated at process start in most cases).

The stack is 'hot'. It's being continually accessed. Allocate a few small objects on the stack and they will be in the CPU's L1 caches and will effectively never consume bandwidth being flushed to L2.

In addition, the stack is thread-local storage, which inherently eliminates a whole bunch of inefficiencies in potential cache transitions - heap management in threaded systems is a cast-iron performance nightmare - and saves you from most of the obvious thread safety bugs (although if you're a complete idiot and try and pass stack objects across threads you can still dig yourself a nice big hole to fall in).
User avatar
KlaxonCow
Baaaaaaa!
Baaaaaaa!
Posts: 8444
Joined: Fri Dec 13, 2002 3:46 am 0
Location: A large grassy field.

Re: Discussion with my bro a = new Banana; VS a = new Banana

Post by KlaxonCow »

Yes, I've explicitly tested it.

With some code I've got that overrides "::operator new", does "placement new" and a whole bunch of mad stuff with its own allocators - so if there is any difference here, this code will surely test it somewhere and fall over.

Changing "World world = new World();" to "World world = new World;" does nothing. My memory allocation stuff can write to the debug log - so I can verify things are doing what they ought to - and the call sequence is the same.

There is no difference whatsoever, as far as I can find. It's identical.

This is just optional syntax.

Like "void function()" or "void function(void)". In C++, if you leave out the parameters, then it's "void" by default anyway. But you can put the "void" in there, if you want to be explicit. The compiler will end up interpreting the same way anyway.

It's not a special invocation. It's just optional syntax. If you do have parameters, then those do need to be in parentheses, of course. But if you don't, then you can either use "()" - an empty parameter list - or just don't use parentheses at all - which means the same thing. It's just flexible about the syntax.
"Words ought to be a little wild - for they are assaults of thought on the unthinking."
- John Maynard Keynes
Post Reply