#22548 - sgeos - Thu Jun 24, 2004 1:19 am
Aside from when I was learning the syntax, I have never had any reason to use a union. How often does anyone else use them?
-Brendan
#22549 - tepples - Thu Jun 24, 2004 1:36 am
SBC workers use them ;-)
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.
#22551 - Miked0801 - Thu Jun 24, 2004 1:38 am
Unions are useful in situations where memory is tight or where you need to map the output of a hardware device depending on registers or headers. For the first, imagine you have a title where there are a bunch of different mini-games. Each mini-game needs to know nothing about the other games. Instead of creating 7 different sets of structures per game and statically allocating the RAM 7 times, you put the structs into a union and the compiler only allocates the memory once thus saving memory (or you dynamically allocate, but I digress). For my second, image that when setting a HW register, the type of memory being accessed changes (like GBA vram when you go from a text mode to a bmp mode.) You can still access the memory in a unified way with a union. You just set one entry to the text style and another to the bmp style. This also helps to hide the HW level and makes things easier to code.
Mike
#22554 - poslundc - Thu Jun 24, 2004 2:51 am
I usually use #defines to accomplish what I could with unions, in order to avoid having to use their syntax. Unions are probably a much cleaner, more professional way, though. ;)
For the record, the need doesn't arrive often. The main reason I can think of has to do with what Mike said, when I want a static pool of memory for multiple purposes that don't overlap, and I want to be able to treat that pool of memory as a different structure depending on the situation.
And tepples, I thought you were supposed to have Asperger's...? Again, ;)
Dan.
#22556 - jma - Thu Jun 24, 2004 2:59 am
Unions are almost unbeatable in embedded systems where space is at a premium. For example, they allow you to access byte ordering as it was meant to be:
Code: |
union uint32 {
unsigned long u32;
unsigned char b[4];
}; |
This lets you access the same 32-bit number without shifting, bit swapping, etc, and gives you the proper byte ordering. Very handy. There are lots of other similar uses. And BTW, using unions to handle variant data types is a waste, IMHO, but I'm sure others here would disagree with me.
Jeff
_________________
massung@gmail.com
http://www.retrobyte.org
#22558 - dagamer34 - Thu Jun 24, 2004 4:09 am
Here's one example that actually makes sense and doesn't take that long to read. :)
Use unions when memory overlaps and has multiple uses, but not at a single time. A perfect example would be attribute 1 of OAM memory and how it is different depending on whether the sprite is in rot/scale mode or not.
_________________
Little kids and Playstation 2's don't mix. :(
#22561 - sajiimori - Thu Jun 24, 2004 6:08 am
I use unions for objects when implementing dynamically-typed languages. In such languages, it is the value that carries a type, not the variable.
Code: |
enum
{
TYPE_INT,
TYPE_STRING,
...
}
struct Object
{
int type;
union
{
int i;
char* s;
...
} value;
};
|
#22565 - sgeos - Thu Jun 24, 2004 10:10 am
Miked0801 wrote: |
(or you dynamically allocate, but I digress) |
What about dynamic allocation do you dislike?
-Brendan
#22575 - Nessie - Thu Jun 24, 2004 3:15 pm
Just from that comment, it's not clear to me that he was either for or against dynamic allocation. However, one reason a person might not like dynamic allocation is that, on a machine that does not have virutal memory, say like the GBA, dynamic allocation can fail.
So, in some ways I prefer the approach of allocating all needed assets for a given level at the time that this level is loaded.
It may take more time to prep the level, but at least then you will know that the allocation is failing (if you are doing your job), and this problem should be 100% reproducible (the same allocations happen at the 'load' of this level, right?) and therefore trivial to address.
Otherwise, I guess you run the risk of a potentially essential dynamic allocation being able to fail when Venus and Mars align on a late Friday afternoon, but only when it's an even numbered day. ...which is my way of saying, rare combinations of events creating a situation where you can encounter an allocation failure.
#22576 - poslundc - Thu Jun 24, 2004 3:31 pm
On the GBA, I have yet to ever use dynamic allocation for anything. For dynamic structures such as linked lists I just use fixed-size chunks of memory (such that the list has a pre-determined maximum length).
Why bother running the risk of heap fragmentation or running out of memory, I figure, when it's just about as easy for me to manage the memory myself.
Dan.
#22578 - jma - Thu Jun 24, 2004 3:55 pm
sajiimori wrote: |
I use unions for objects when implementing dynamically-typed languages. In such languages, it is the value that carries a type, not the variable.
<code snipped> |
This is what I meant by variant data types. This method of doing it is just wrong (not to be rude). The problem is that all your created "objects" will all have the size of the largest one in your variant type (or union).
Variant data types are much better implemented with just a (void*) to somewhere in memory. Or, if designing an actual language (like Lisp) that has dynamic typing, use a large hunk of memory to hold all your variables, like a mempool, and then just index into it the exact size of each object.
Your method is simple and quick, but is a waste of memory. On a desktop, that's probably alright. But on another system (like a console) you're just throwing away bits...
Jeff
_________________
massung@gmail.com
http://www.retrobyte.org
#22579 - poslundc - Thu Jun 24, 2004 4:23 pm
jma wrote: |
This is what I meant by variant data types. This method of doing it is just wrong (not to be rude). The problem is that all your created "objects" will all have the size of the largest one in your variant type (or union). |
But that's the point of a union: to interpret the same chunk of data through different data types.
If one of your data types doesn't use all the memory, and that's a problem for you, then you should probably be using dynamic allocation.
Quote: |
Variant data types are much better implemented with just a (void*) to somewhere in memory. |
You still need to allocate memory for the object. How are you going to allocate it? If you're using static allocation, it's no different from a union.
Personally, I think dynamic allocation is for chumps. :D
Dan.
#22581 - Miked0801 - Thu Jun 24, 2004 5:03 pm
Actually, I'll usually use dynamic allocation over unions myself. The only place I don't is when allocating in IWRAM where dynamic memory block headers just eat too much space. Nothing's free...
#22582 - jma - Thu Jun 24, 2004 5:25 pm
poslundc wrote: |
jma wrote: | This is what I meant by variant data types. This method of doing it is just wrong (not to be rude). The problem is that all your created "objects" will all have the size of the largest one in your variant type (or union). |
But that's the point of a union: to interpret the same chunk of data through different data types. |
Yes, but on an embedded system, you want to make sure that you aren't wasting space. If you are statically allocating space in a union (for example, an 80 character string) that could also be a 2-byte short, you're killing yourself.
Quote: |
If one of your data types doesn't use all the memory, and that's a problem for you, then you should probably be using dynamic allocation. |
No, you can still use static allocation, just create pointers to them. Of course, now we're not talking about unions any more :) But this is much more memory friendly -- and much faster than dynamically allocating memory on the fly.
Quote: |
You still need to allocate memory for the object. How are you going to allocate it? If you're using static allocation, it's no different from a union. |
Using C++ for readability's sake (I would use C in real life):
Code: |
MemoryPool mem_pool(2048); // allocate 2K at start of program
struct Variant {
enum TYPE type;
void *value;
};
int main() {
Variant x,s;
x.type = TYPE_INT;
x.value = mem_pool.request(4); // get 4 byte pointer
s.type = TYPE_STRING;
s.value = mem_pool.request(256); // get 256 byte pointer
} |
In the above example, no space has been wasted and the memory is statically allocated at the start of the program. Using unions for the above example, x and s would both take up 260 bytes (4 byte type and 256 bytes of value data).
Using a MemoryPool style class or API, you can allow yourself to align data as well, release the data when done with it, etc. All very fast, without dynamic allocation.
Jeff
_________________
massung@gmail.com
http://www.retrobyte.org
#22585 - sajiimori - Thu Jun 24, 2004 5:39 pm
Quote: |
The problem is that all your created "objects" will all have the size of the largest one in your variant type (or union).
|
...which is a pointer. Compound or boxed objects have seperate structures. This indirection is what makes closures work.
Quote: |
Variant data types are much better implemented with just a (void*) to somewhere in memory.
|
Yeah, and I could cast it to int or char when it's a constant, but unions are nicer.
Also, for fast symbol tables (implemented as arrays where symbols are ints), the reference objects need to all be the same size. As is so often the case, there is a tradeoff between space and time when working with larger objects.
Edit:
Quote: |
In the above example, no space has been wasted and the memory is statically allocated at the start of the program.
|
I might say you wasted 4 bytes for the int, when you could have stored it in the void*. Maybe you were about to capture it in a closure. ;)
#22586 - jma - Thu Jun 24, 2004 6:00 pm
Yes, but right now we're talking about trivial examples. However, things get a lot more complicated when you are talking about sound bytes, character models, and entire game levels instead of just int, string, char...
I think we understand each other, and if we happen to disagree, that's fine -- we don't have to work with each other's code ;) However, as a last little "argument":
Unions in C also have the disadvantage that they only accept the first member of the union for data setting and it is what the compiler uses. So, for example, the following union:
Code: |
union variant {
char c;
void *p;
char *string;
int x;
} |
Is not guaranteed to be 4-byte aligned (because the byte is the first member). So care must be taken when declaring a union. Also, you can't set it at compile time:
Code: |
// using above variant union
const variant vars[] = { "Hello, world", 1000 }; |
This is invalid, because it is only looking at the 'c' member of the union. This can be a real pain in the neck. My code method can take care of this by just putting addresses into the array. However, this is more of a hack-solution than a real one.
While this is hardly a "serious" argument one way or the other, I figured these were a couple "hic-ups" that the OP should be aware of when using unions.
Jeff
_________________
massung@gmail.com
http://www.retrobyte.org
#22599 - sajiimori - Thu Jun 24, 2004 11:58 pm
Just when I thought I knew C! heheh
#22604 - sgeos - Fri Jun 25, 2004 3:23 am
Nessie wrote: |
Just from that comment, it's not clear to me that he was either for or against dynamic allocation. |
Before reading this thread, I would have used dynamic allocation to tackle a bunch of mini games. (Actually I would put each game in a separate routine complete with local variables.) I had not even considered unions and figured that there must be a reason why dynamic allocation is not the Obvious Choice.
Quote: |
in some ways I prefer the approach of allocating all needed assets for a given level at the time that this level is loaded. |
This is an everything at once version of dynamic allocation. Although the question has been answered, I meant to ask "Are there any reasons why one would use a union instead of some version of dynamic allocation?"
Quote: |
It may take more time to prep the level, |
What might one do that would take more than an extra frame or two of prep time?
-Brendan
#22622 - Nessie - Fri Jun 25, 2004 3:23 pm
Quote: |
This is an everything at once version of dynamic allocation. |
The way I see it, there is dynamic allocation...and then there is dynamic allocation.
Sort of OT, I worked on a console title that used static allocation. Basically it was an STL-like implementation that provided fixed-sized containers, with the container size being specified compile time by the programmer.
I guess the idea was to try and provide the flexibility of STL with a system that was designed to eliminate heap fragmentation problems. Another design goal was to provide safety checking that might not be present with access to raw arrays, maps, lists, etc.
However, there were a few annoying effects of this decision. I'll leave it as an exercise for the interested reader to figure out the problems with this approach. :) Of course there were some good things about it as well...
Anyway, to get back to the original statement. I don't have a problem with dynamic alloaction. I would just tend to prefer that it happens in a more predicatable way to make it easier to detect potential problems, ie, up-front in a level.
#22623 - poslundc - Fri Jun 25, 2004 3:54 pm
Nessie wrote: |
Sort of OT, I worked on a console title that used static allocation. Basically it was an STL-like implementation that provided fixed-sized containers, with the container size being specified compile time by the programmer. |
That is pretty much what I do for my "dynamic" entities. (Except I don't bother with templates or any of the OO baggage that comes with STL.)
IMO, "real" dynamic allocation is just not worth the trouble more often than not when programming for a low-level embedded console.
Dan.