Ogre #defines operator new? Say it isn't so.

Anything and everything that's related to OGRE or the wider graphics field that doesn't fit into the other forums.
jabba
Kobold
Posts: 25
Joined: Mon Jun 30, 2003 7:50 pm

Post by jabba » Wed Dec 20, 2006 7:41 pm

edit: off topic post that was moved
Last edited by jabba on Wed Dec 20, 2006 9:24 pm, edited 1 time in total.
0 x

User avatar
Falagard
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 2060
Joined: Thu Feb 26, 2004 12:11 am
Location: Toronto, Canada
Contact:

Post by Falagard » Wed Dec 20, 2006 8:04 pm

Maybe I should have put this in the help section but it seemed related.
Put it in the help section, it's not related to the current thread on global new and delete overloading and memory tracking in Ogre.
0 x

TwoBit
Kobold
Posts: 30
Joined: Sun Dec 17, 2006 5:48 am

Post by TwoBit » Wed Dec 20, 2006 8:59 pm

I should be clear that we are not talking about proposing EASTL or anything in it as a standard. We are talking about descibing to the C++ community what the issues are that face game and embedded device programmers and how we addressed them.
You tested against libstdc++, stlport as well as the Dinkumware implementation shipped with VC++?
Correct. And Metrowerks as well. And Dinkumware for Playstation 3. I can tell you a lot about commercial STL implementions. Of the commercial implementations, in a nutshell: Dinkumware is the most correct but the worst performing, Metrowerks is the most readable, libstdc++ is the highest performing (and nearly as correct as Dinkumware), and stlport is the most portable (it's the only one that's portable).
By rewrote, do you mean supplied a new implementation of the same interface?
For containers that are the same as std STL containers, the interface is nearly identical. The only change is how allocators work. However, additional functions were added to std containers where useful. For example, vector has set_capacity(size) to force a capacity, has push_back(void) to allow faster operation on large objects, and has a data() function to return the address of the first element. Additionally with vector, requirements are made more practical: a vector is guaranteed to be a contiguous linear array, vector<bool> really is an array of bool, and iterators are guaranteed to be pointers. EASTL provides a guarantee that existing use of a std STL container with the default allocator will behave under EASTL according to C++ std for regular STL.

For containers that are not the same as std STL containers, they have a new interface but this new interface is consistent with existing STL conventions. Such containers include intrusive_list, fixed_string, vector_map, and ring_buffer.
0 x

User avatar
Praetor
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 3335
Joined: Tue Jun 21, 2005 8:26 pm
Location: Rochester, New York, US
Contact:

Post by Praetor » Wed Dec 20, 2006 9:15 pm

I don't mean to be condescending, but it really is starting to sound like a case of Not Invented Here. I run into the same thing at all sorts of companies, for all sorts of reasons. Rewriting the STL seems like the most common case of this. In a custom 2D engine I'm working on now, the STL map used for binding shader constants is high on the list of time-spenders in my profiles. Now, I could blame the STL implementation and completely rewrite my own, or I could be a little creative and actually analyze my storage practices, and more importantly, the algorithm I devised to do the binding. You may find rethinking algorithms gives you way more speed-boost per developer hour than reinventing the wheel. Again. And again.
0 x

User avatar
Game_Ender
Ogre Magi
Posts: 1269
Joined: Wed May 25, 2005 2:31 am
Location: Rockville, MD, USA

Post by Game_Ender » Wed Dec 20, 2006 9:17 pm

Very cool, thanks for the information TwoBit I look forward to your paper and thanks for dropping by our forums.

EDIT: If its actually faster than the libstdc++ but keeps almost the same interface, why not entertain learning how it was done. Maybe some of its tricks can be implemented in other STL libraries.
0 x

User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19261
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
Contact:

Post by sinbad » Thu Dec 21, 2006 12:31 am

Whilst I have nowhere near the experience of TwoBits, I'm aware that you can do a hell of a lot with the regular STL if you're smart about choosing containers and algorithms, and on consoles particularly with a view to cache friendliness. I have a natural aversion to rewriting something that has been heavily tested and profiled in the wild, but I also recognise that consoles are something of a special case for many things - they may be getting more powerful and general purpose, but their memory restrictions still suck ;) I'll be very interested to read this paper, Scott Meyers Effective STL got me enthused about good STL usage some years back and feeding back into that is definitely a good thing. Plus, if it stops some developers tossing aside the STL entirely and writing their own completely proprietary containers with their own personal interfaces, that has to be a good thing ;)

I'm certainly of the view that plenty of developers avoid re-use just because they like to reinvent, and are perhaps a little arrogant that they can do it better than anyone else (young developers are always very prone to this I find) - resulting in set of reinvented features with questionable benefits over the original. In many ways this is where open source wins, because rather than reinvent, the genuine improvements can be folded back in without creating 10 different, all slightly ragged solutions to a single problem. This sounds like a much more measured approach though, and I'm glad you chose to stick to the STL interface and hopefully your research can help improve STL implementations for all.
Last edited by sinbad on Thu Dec 21, 2006 12:44 am, edited 1 time in total.
0 x

User avatar
xavier
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 9481
Joined: Fri Feb 18, 2005 2:03 am
Location: Dublin, CA, US

Post by xavier » Thu Dec 21, 2006 12:38 am

Praetor wrote:I don't mean to be condescending, but it really is starting to sound like a case of Not Invented Here. I run into the same thing at all sorts of companies, for all sorts of reasons. Rewriting the STL seems like the most common case of this. In a custom 2D engine I'm working on now, the STL map used for binding shader constants is high on the list of time-spenders in my profiles. Now, I could blame the STL implementation and completely rewrite my own, or I could be a little creative and actually analyze my storage practices, and more importantly, the algorithm I devised to do the binding. You may find rethinking algorithms gives you way more speed-boost per developer hour than reinventing the wheel. Again. And again.
I am highly allergic to Reinventing The Wheel, and I have yet to work at a company where they don't do it, primarily (and admittedly) for Not Invented Here reasons.
0 x
Do you need help? What have you tried?

Image

Angels can fly because they take themselves lightly.

TwoBit
Kobold
Posts: 30
Joined: Sun Dec 17, 2006 5:48 am

Post by TwoBit » Thu Dec 21, 2006 12:57 am

I understand what you are saying about NIH, and in fact we resisted making EASTL for about five years. But eventually the reasons became overwhelming. The biggest reason was in fact the painful (indeed virtually unusable) custom allocator system in std STL. The myriad of incompatible hash table versions was another. Let me say this, though I must apologize for it sounding brash: I know of nobody who has used EASTL and who would want to go back to std STL.

Here is an excerpt from one of our documents:

* Some STL implementations (especially Microsoft STL) have inferior performance characteristics that make them unsuitable for game development. EASTL is faster than all existing STL implementations.
* The STL is hard to debug, as most STL implementations use cryptic variable names and unusual data structures.
* STL allocators are painful to work with, as they have many requirements and cannot be modified once bound to a container.
* STL allocators make it very difficult to track memory usage, as the STL allocator design virtually assumes that all memory allocation is globally derived.
* The STL is implemented with very deep function calls. This results is unacceptable performance in non-optimized builds and sometimes in optimized builds as well.
* The STL doesn't support nor recognize the concept of alignment requirements.
* STL containers won't let you insert an entry into a container without supplying an entry to copy from. This can be inefficient.
* Useful STL extensions (e.g. slist, hash_map, shared_ptr) found in existing STL implementations such as STLPort are not portable because they don't exist in other versions of STL or aren't consistent between STL versions.
* The STL lacks useful extensions that game programmers find useful (e.g. intrusive_list) but which could be best optimized in a portable STL environment.
* The STL has specifications that limit our ability to use it efficiently. For example, STL vectors are not guaranteed to use contiguous memory and so cannot be safely used as an array.
* STL algorithms in current commercial implementations don't preserve template types, leading to unacceptable performance in some cases.
* The STL puts an emphasis on correctness before performance, whereas sometimes you can get significant performance gains by making things less academically pure.
* STL containers have private implementations that don't allow you to work with their data in a portable way, yet sometimes this is an important thing to be able to do (e.g. node pools).
* All existing versions of STL allocate memory in empty versions of at least some of their containers. This is not ideal and prevents optimizations such as container memory resets that can greatly increase performance in some situations.
* There are legal issues that make it hard for us to freely use portable STL implementations such as STLPort.
* We have little say in the design and implementation of the STL and so are unable to change it to work for our needs.
0 x

User avatar
Praetor
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 3335
Joined: Tue Jun 21, 2005 8:26 pm
Location: Rochester, New York, US
Contact:

Post by Praetor » Thu Dec 21, 2006 1:31 am

That's a nice list. I think it is very telling that you at least keep the major features of the STL interface. I suppose the one you focused on (the custom allocators) seems to be a serious design flaw in std STL. Design flaws are definitely serious. Something that does everything STL does, but with a mind towards performance and portability, is definitely a topic that interests me.
0 x

User avatar
xavier
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 9481
Joined: Fri Feb 18, 2005 2:03 am
Location: Dublin, CA, US

Post by xavier » Thu Dec 21, 2006 1:35 am

The only reason I can see for maintaining interface compatibilty with STL is existing codebase. Otherwise, I would say scrap it and just use your own interfaces.
0 x
Do you need help? What have you tried?

Image

Angels can fly because they take themselves lightly.

User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19261
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
Contact:

Post by sinbad » Thu Dec 21, 2006 11:52 am

xavier wrote:The only reason I can see for maintaining interface compatibilty with STL is existing codebase. Otherwise, I would say scrap it and just use your own interfaces.
Not just current codebase, future code integration, training of future coders etc. Fact is that standards are a good thing, especially when you look beyond the here & now.
0 x

User avatar
xavier
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 9481
Joined: Fri Feb 18, 2005 2:03 am
Location: Dublin, CA, US

Post by xavier » Thu Dec 21, 2006 5:35 pm

sinbad wrote:
xavier wrote:The only reason I can see for maintaining interface compatibilty with STL is existing codebase. Otherwise, I would say scrap it and just use your own interfaces.
Not just current codebase, future code integration, training of future coders etc. Fact is that standards are a good thing, especially when you look beyond the here & now.
In EA's case, was I speaking (and now apparently I am speakng like Yoda... :? ). Any moderately experienced software engineer would be able to pick up a proprietary container library interface pretty quickly.
0 x
Do you need help? What have you tried?

Image

Angels can fly because they take themselves lightly.

klauss
Hobgoblin
Posts: 559
Joined: Wed Oct 19, 2005 4:57 pm
Location: LS87, Buenos Aires, República Argentina.

Post by klauss » Thu Dec 21, 2006 6:17 pm

1) Placement new. It's a usage of the new operator that helps in implementing pools - not that I ever replaced the standard new with one which pooled, that'd be... recursive... Ogre's MM cannot live in the same source with such a thing, and that becomes a bit of a pain at times.

2) Some of the problems listed are inexistent, though. Like, for set_capacity(), you have reserve() which I've tested and used a lot, and many implementations support (perhaps not all, I don't think it's standard). I've worked around data() by using a hack which, I give you, is completely unsafe: &vec[0]. But sometimes speed mandates... Of course, the safe (and c++) way to do that, is to modify the function requiring the pointer, to require a random access iterator instead. But... you can't do that sometimes... when it's not your API :( - push_back() can be implemented with v.resize(v.size()+1), and some others like that. I've used allocators, for instance, and yes, they're a pain, but they're usable. I don't know yours, maybe yours are a hundred times easier to use, and that'd be good, but not worth an STL rewrite IMHO.

I have to agree with Praetor... when the STL is your enemy, it's probably because you're misusing it. Not necessarily, but probably.

BTW: Anyone ever tried to port libstdc++ into other compilers... like MSVC? That would probably be useful...
0 x
Oíd mortales, el grito sagrado...
Hey! What is it with that that?
Wing Commander Universe

TwoBit
Kobold
Posts: 30
Joined: Sun Dec 17, 2006 5:48 am

Post by TwoBit » Thu Dec 21, 2006 8:45 pm

2) Some of the problems listed are inexistent, though. Like, for set_capacity(), you have reserve()
set_capacity != reserve. reserve will never reduce the capacity of the container, it will only increase it; also it is a hint and not a directive and so it can set the actual capacity to whatever it wants >= to the size you pass in. This has been a problem for enough people that there is ugly thing called the "swap trick" to work around it. set_capacity unilaterally sets the capacity to whatever the user specifies, with no tricks.
push_back() can be implemented with v.resize(v.size()+1), and some others like that.
push_back != resize(size+1). The latter necessarily creates a temporary (the standard in fact specifies so) while the former does not. This temporary creation avoidance is the whole point push_back(void).

Most of std STL is fine and very well designed. We wouldn't be re-writing it if it had a usable allocator scheme. However, the allocator scheme in std STL is broken beyond usability, and many people in the C++ community realize it and admit it. In professional games programming, custom allocators are the rule and not the exception. This creates a virtually untenable situation with std STL.
0 x

klauss
Hobgoblin
Posts: 559
Joined: Wed Oct 19, 2005 4:57 pm
Location: LS87, Buenos Aires, República Argentina.

Post by klauss » Thu Dec 21, 2006 9:19 pm

TwoBit wrote:
2) Some of the problems listed are inexistent, though. Like, for set_capacity(), you have reserve()
set_capacity != reserve. reserve will never reduce the capacity of the container, it will only increase it; also it is a hint and not a directive and so it can set the actual capacity to whatever it wants >= to the size you pass in. This has been a problem for enough people that there is ugly thing called the "swap trick" to work around it. set_capacity unilaterally sets the capacity to whatever the user specifies, with no tricks.
Fair enough - though that arbitrary freedom it has is there for performance reasons... but I can imagine you want that control because you have limited memory and you don't want to waste a single entry or something like that.
TwoBit wrote:
push_back() can be implemented with v.resize(v.size()+1), and some others like that.
push_back != resize(size+1). The latter necessarily creates a temporary (the standard in fact specifies so) while the former does not. This temporary creation avoidance is the whole point push_back(void).
Wait... how do you avoid creating a temporary?
Resize only initializes new entries, which is really needed, unless you can live with badly initialized objects (which I doubt, and is in any case very dangerous).
TwoBit wrote:Most of std STL is fine and very well designed. We wouldn't be re-writing it if it had a usable allocator scheme. However, the allocator scheme in std STL is broken beyond usability, and many people in the C++ community realize it and admit it. In professional games programming, custom allocators are the rule and not the exception. This creates a virtually untenable situation with std STL.
You mean unusable as in ugly and difficult to handle, or unusable in unable to do certain stuff?
0 x
Oíd mortales, el grito sagrado...
Hey! What is it with that that?
Wing Commander Universe

TwoBit
Kobold
Posts: 30
Joined: Sun Dec 17, 2006 5:48 am

Post by TwoBit » Fri Dec 22, 2006 7:26 am

I've worked around data() by using a hack which, I give you, is completely unsafe: &vec[0]
data() != &vec[0]. The latter is an element dereference which will trigger an assertion failure if the container is empty. The former will simply safely return NULL.
Wait... how do you avoid creating a temporary?
You allocate the new space internally and default-construct it in place. This is impossible to do externally; it has to be done by the vector (and list and deque, etc.) implementation.
You mean unusable as in ugly and difficult to handle, or unusable in unable to do certain stuff?
std STL allocators are defined per-stored-class type and not per container instance. You cannot have a container class that ever uses more than one allocator, not without some painful hacking around the allocator definition. And to add insult to injury, std STL container don't let you access the container's allocator; you can only get a copy of it.

Consider this: to have the knowledge and experience to completely implement a conforming STL implementation one needs to understand the STL and the C++ standard rather well. Somebody in such a position would not likely misunderstand the STL in such a way as to misjudge its strengths and weaknesses.
0 x

User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7144
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 14

Post by Kojack » Fri Dec 22, 2006 3:01 pm

TwoBit wrote:The STL has specifications that limit our ability to use it efficiently. For example, STL vectors are not guaranteed to use contiguous memory and so cannot be safely used as an array.
Well, ISO 14882 section 23.2.4 says that all vectors (besides vector<bool>) use contiguous storage for elements.
0 x

User avatar
sinbad
OGRE Retired Team Member
OGRE Retired Team Member
Posts: 19261
Joined: Sun Oct 06, 2002 11:19 pm
Location: Guernsey, Channel Islands
Contact:

Post by sinbad » Fri Dec 22, 2006 4:55 pm

TwoBit wrote:set_capacity != reserve. reserve will never reduce the capacity of the container, it will only increase it; also it is a hint and not a directive and so it can set the actual capacity to whatever it wants >= to the size you pass in. This has been a problem for enough people that there is ugly thing called the "swap trick" to work around it. set_capacity unilaterally sets the capacity to whatever the user specifies, with no tricks.
I thought the 'swap trick' was a universally recognised idiom by now :)
Most of std STL is fine and very well designed. We wouldn't be re-writing it if it had a usable allocator scheme. However, the allocator scheme in std STL is broken beyond usability, and many people in the C++ community realize it and admit it. In professional games programming, custom allocators are the rule and not the exception. This creates a virtually untenable situation with std STL.
Hmm, that kinda throws cold water over some of Shoggoth's plans. Is it really as bad as that? Stuff I've found on the subject doesn't paint such a bad picture, e.g. these GDC notes: www.tantalon.com/pete/customallocators.ppt . Another discussion on GameDev recently was also not so damning in most posts: http://www.gamedev.net/community/forums ... _id=424168 (one person claimed to be using the STL without custom allocators on the DS, which I found quite surprising). There does seem some split in opinion on how bad the situation is, probably along the lines of how tight the requirements are.

[edit]
You cannot have a container class that ever uses more than one allocator, not without some painful hacking around the allocator definition. And to add insult to injury, std STL container don't let you access the container's allocator; you can only get a copy of it.
Ah, I was aware of the former (and had some formative thoughts on how to make the allocators pluggable in Ogre despite this), but not the latter. I can see how that can be a pain in the ass, especially for what I wanted to do. Hrm.
Last edited by sinbad on Fri Dec 22, 2006 5:24 pm, edited 1 time in total.
0 x

klauss
Hobgoblin
Posts: 559
Joined: Wed Oct 19, 2005 4:57 pm
Location: LS87, Buenos Aires, República Argentina.

Post by klauss » Fri Dec 22, 2006 5:03 pm

TwoBit wrote:
Wait... how do you avoid creating a temporary?
You allocate the new space internally and default-construct it in place. This is impossible to do externally; it has to be done by the vector (and list and deque, etc.) implementation.
Resize does that. I sincerely don't see your point. (*)
TwoBit wrote:
You mean unusable as in ugly and difficult to handle, or unusable in unable to do certain stuff?
std STL allocators are defined per-stored-class type and not per container instance. You cannot have a container class that ever uses more than one allocator, not without some painful hacking around the allocator definition. And to add insult to injury, std STL container don't let you access the container's allocator; you can only get a copy of it.

Consider this: to have the knowledge and experience to completely implement a conforming STL implementation one needs to understand the STL and the C++ standard rather well. Somebody in such a position would not likely misunderstand the STL in such a way as to misjudge its strengths and weaknesses.
Try not to misunderstand the question here - I just want to know. I didn't work with allocators that much - only once, to make a container use a pool, rather than the usual new, but that's it. I'm not accusing you of anything, I just want to benefit from your experience.

(*) EDIT: I just checked gcc's implementation, and it does not do that. Weird, MSVC's implementation does. I now see your point. ;)
0 x
Oíd mortales, el grito sagrado...
Hey! What is it with that that?
Wing Commander Universe

TwoBit
Kobold
Posts: 30
Joined: Sun Dec 17, 2006 5:48 am

Post by TwoBit » Fri Dec 22, 2006 8:51 pm

Well, ISO 14882 section 23.2.4 says that all vectors (besides vector<bool>) use contiguous storage for elements.
I, as well as the C++ standardization committee, believe you are misinterpreting the standard. See, for example, this: http://groups.google.com/group/comp.lan ... 8&oe=UTF-8

What paragraph from section 23.2.4 makes you believe it is contiguous? The question of whether to change vector to guarantee contiguity has been in debate by the committee and C++ community for the next C++ standard revision.
0 x

TwoBit
Kobold
Posts: 30
Joined: Sun Dec 17, 2006 5:48 am

Post by TwoBit » Fri Dec 22, 2006 9:21 pm

Hmm, that kinda throws cold water over some of Shoggoth's plans. Is it really as bad as that? Stuff I've found on the subject doesn't paint such a bad picture, e.g. these GDC notes: www.tantalon.com/pete/customallocators.ppt . Another discussion on GameDev recently was also not so damning in most posts: http://www.gamedev.net/community/forums ... _id=424168 (one person claimed to be using the STL without custom allocators on the DS, which I found quite surprising). There does seem some split in opinion on how bad the situation is, probably along the lines of how tight the requirements are.
I don't want to try to discourage you or anybody here from using the STL in Ogre or in game code. The analysis of the std STL allocator scheme is not simple and the conclusions are not black and white. We have found that its scheme is difficult enough as to make it hard to recommend for EA-wide use, but you may well be able to make it work within a narrower and more controlled environment such as Ogre. Custom allocators can be made to work with some effort to work around STL's value semantics.

I wonder if the best way to work around the problem is to create an OgreAllocator interface class and a default implementation of that class which uses new/delete. Then define an Ogre STL allocator template which uses this interface; and you have all Ogre containers use a specialization of this template. The result is that users are largely shielded from STL allocator issues and the dreaded rebind and instead can work with a generic OgreAllocator interface.

Lastly, there *is* a trick to allow std STL to work with more than one allocator per container type; it involves having an allocator that has a pointer to the address of an allocator. I can sketch it out.

It might be useful to move such a discussion to a separate thread.
0 x

User avatar
haffax
OGRE Retired Moderator
OGRE Retired Moderator
Posts: 4823
Joined: Fri Jun 18, 2004 1:40 pm
Location: Berlin, Germany
Contact:

Post by haffax » Fri Dec 22, 2006 9:56 pm

The discussion whether std::vector has to have its data stored continuously or not is purely academic. All implementations that are taken seriously do it this way. In fact much code out there wouldn't work if it weren't.
0 x
team-pantheon programmer
creators of Rastullahs Lockenpracht

klauss
Hobgoblin
Posts: 559
Joined: Wed Oct 19, 2005 4:57 pm
Location: LS87, Buenos Aires, República Argentina.

Post by klauss » Sat Dec 23, 2006 1:47 am

Ok, sorry for asking what everybody seems to know... but what do you mean by "more than one allocator per container type"?

On most implementations I've seen, constructors take a last argument with an allocator, so you can always pass a specific instance of an allocator. That would make it possible to use many allocators... or are you talking about something else?
0 x
Oíd mortales, el grito sagrado...
Hey! What is it with that that?
Wing Commander Universe

User avatar
Kojack
OGRE Moderator
OGRE Moderator
Posts: 7144
Joined: Sun Jan 25, 2004 7:35 am
Location: Brisbane, Australia
x 14

Post by Kojack » Sat Dec 23, 2006 4:12 am

What paragraph from section 23.2.4 makes you believe it is contiguous?
From the standard:
23.2.4 Class template vector [lib.vector]
1 A vector is a kind of sequence that supports random access iterators. In addition, it supports (amortized)
constant time insert and erase operations at the end; insert and erase in the middle take linear time. Storage
management is handled automatically, though hints can be given to improve efficiency. The elements of a
vector are stored contiguously
, meaning that if v is a vector<T, Allocator> where T is some type
other than bool, then it obeys the identity &v[n] == &v[0] + n for all 0 <= n < v.size().
Hmm, I'm misinterpreting "The elements of a vector are stored contiguously" to think they are stored contiguously? :)
The question of whether to change vector to guarantee contiguity has been in debate by the committee and C++ community for the next C++ standard revision
Yes, and that "next C++ standard revision" came out 3 years ago.

According to Bjarne Stroustrup 2 years ago, in response to the exact same link you provided:
Yes. The 2003 "technical corrigendum" corects that. 23.2.4[1] says
"The elements of a vector are stored contiguously".

As Russell pointed out, this was always the intent and all
implementations always did it that way
0 x

Post Reply