Skip to main content
gamedev.net
🔒 Locked

Mixing different variable types

Started by Russell 12 replies 350+ views
Original Post
Russell
Russell
I have recently had some problems with mixing different variable types (ex. mixing signed with unsigned, char with short with long, etc.), and I am wondering if there are any "rules" or anything as for when it is ok to mix these different types. The main problem areas are the different data type widths. An example. I have one struct that uses chars instead of ints to conserve space, because I have a big array of these, and I copy them a lot.
typedef struct {
    unsigned char a;
    unsigned char b;
    unsigned char c;
    unsigned char d;
} MyStruct;
 
I also use a union to access different parts of this struct easier. [code typedef union { MyStruct my_struct; unsigned short first_half; unsigned long whole_thing; } MyUnion; I use a few of these values as the index into other arrays. The other arrays are 256 elements in size, and I never use any element higher than 191 (around there). I am getting weird behavior though. If I use the unsigned char as the index, everything breaks. When I use a regular int, all works fine. I''ve heard in the past that you should use always use the default size of the machine (32-bits here) as an index into an array or when computing pointer offsets. Anyone else heard this, or know the reason for this, or know why my program breaks when I use an 8-bit value instead of a 32-bit? It doesn''t crash, but it doesn''t do what it''s supposed to do.
ToohrVyk
ToohrVyk
Erm... didn''t quite understand what you meant in the last paragraph... do you mind posting a source code example where it works, and another where it doesn''t?

ToohrVyk
-------------
Extatica - a free 3d game engine
Available soon!
Click here to learn more
Russell
Russell
This doesn''t work...

void RayTrace (int iterations, char offset, unsigned char bit) {
int i;
unsigned char square;
for (i = square = 0; i < iterations; i++) {
square += offset;
attackable[square + 128] |= bit;
step[square + 128] = offset;
}
}


This does work...

void RayTrace (int iterations, int offset, unsigned flag) {
int i;
int sq;
for (i = sq = 0; i < iterations; i++) {
sq += offset;
attackable[sq + 128] |= flag;
step[sq + 128] = offset;
}
}


It is called from this...

void InitAttacks () {
int c, piece;
for (c = WHITE; c <= BLACK; c++) {
int i;
for (i = 0; i < 2; i++)
RayTrace(1, pawn_dirs[c], piece_bits[c][PAWN]);
}
for (piece = KNIGHT; piece <= KING; piece++) {
int iterations;
char * dir;
iterations = slider[piece] ? 7 : 1;
for (dir = piece_dirs[0][piece]; *dir; dir++)
RayTrace(iterations, *dir, piece_bits[0][piece]);
}
}


Similarly, if I change ''c'' and ''piece'' to unsigned char in InitAttacks(), it breaks.

This is part of an initialization routine that initializes an attack table in a chess program. Each element of attackable[] is a bit field describing which pieces could conceivably attack a square of a given offset (-128, +128). You add 128 to avoid a negative offset. If you want to read more about what I''m doing, go here and read the section "A Bonus" at the bottom.
cgoat
cgoat
One problem you may be having is with your union. Little-endian machines are going to throw off your variables. For example, the value of "first_half" has the high order byte set to the value of b. "whole_thing" has them in the reverse order. Run the following code and you''ll see:


  
#include <cstdio>

struct MyStruct
{
unsigned char a;
unsigned char b;
unsigned char c;
unsigned char d;
};

union MyUnion
{
MyStruct my_struct;
unsigned short first_half;
unsigned long whole_thing;
};

int main(int argc, char *argv[])
{
MyUnion oUnion;

oUnion.my_struct.a = 0xAA;
oUnion.my_struct.b = 0xBB;
oUnion.my_struct.c = 0xCC;
oUnion.my_struct.d = 0xDD;

printf("first_half: %X\n", oUnion.first_half);
printf("whole_thing: %X\n", oUnion.whole_thing);

return 0;
}


I''m not sure if this is what you''re asking about, since I can''t understand that last paragraph either. Are you using the union members as the indexes?
cgoat
cgoat
Ok, I see that''s not the problem... but it''s still good to be aware of.

Your second for loop looks weird. Why is dir a pointer? Is piece_dirs definitely an array of pointers? Is the last pointer definitely 0?
Jan Wassenberg
Jan Wassenberg
Side note:
I think you''d be better off with bitboards (I figure the 0x88 method arose out of a dire lack of memory )

Now back to your problem:
quote:

When I use a regular int, all works fine. I''ve heard in the past that you should use always use the default size of the machine (32-bits here) as an index into an array or when computing pointer offsets. Anyone else heard this, or know the reason for this, or know why my program breaks when I use an 8-bit value instead of a 32-bit?


Stupid question: are you overflowing your 8 bit range? |0x88 + 0x7f| = 9 bits.
Only use char when you need to save space, i.e. large arrays. Loading an 8 bit value into a 32 bit register (mov?x) is slower than a normal move, and 8 bit ops will lead to partial register stalls, which are devastating to Intel CPUs.
For 2-3 auto variables, just use int.

BTW, watch for [ i ] in code

500x3
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
Russell
Russell
quote:
Are you using the union members as the indexes?

No, my actual structure is something like:
struct {
unsigned char color;
unsigned char type;
unsigned char square;
unsigned char dummy;
};

I use the union to grab just the piece info out of that. That also makes transferring the contents of one square to another easier.
quote:
Your second for loop looks weird. Why is dir a pointer? Is piece_dirs definitely an array of pointers? Is the last pointer definitely 0?

BoardOffset white_pawn_dirs[] = { 15, 17, 16, 0 };
BoardOffset black_pawn_dirs[] = { -15, -17, -16, 0 };
BoardOffset knight_dirs[] = { -33, -31, -18, -14, 14, 18, 31, 33, 0 };
BoardOffset bishop_dirs[] = { -17, -15, 15, 17, 0 };
BoardOffset rook_dirs[] = { -16, -1, 1, 16, 0 };
BoardOffset queen_dirs[] = { -17, -16, -15, -1, 1, 15, 16, 17, 0 };
BoardOffset king_dirs[] = { -17, -16, -15, -1, 1, 15, 16, 17, 0 };

BoardOffset * piece_dirs[2][6] = {
{ white_pawn_dirs, knight_dirs, bishop_dirs, rook_dirs, queen_dirs, king_dirs },
{ black_pawn_dirs, knight_dirs, bishop_dirs, rook_dirs, queen_dirs, king_dirs }
};

quote:
I think you''d be better off with bitboards
I have written several chess programs. Currently I am just playing around with different board representations. I''ve done several different kinds of bitboard engines in the past.
quote:
Stupid question: are you overflowing your 8 bit range? |0x88 + 0x7f| = 9 bits.
I''m not actually using the 0x88 board representation (which is 128-elements in size). I''m using a 256 element board, which gives you the benefits of 0x88, with a few small bonuses.
quote:
Only use char when you need to save space, i.e. large arrays. Loading an 8 bit value into a 32 bit register (mov?x) is slower than a normal move, and 8 bit ops will lead to partial register stalls, which are devastating to Intel CPUs.
For 2-3 auto variables, just use int.
The reason I started in on using 8-bit variables is to make it easier to copy the whole structure. In a chess program, you will do a lot of piece copying from square to square, move copying everytime you make a move to back it up, and so on. I thought the 32-bit structure with 8-bit fields would be more efficient in the copying aspect. 1 copy vs. 4. So what would you recommend here?
quote:
BTW, watch for [ i ] in code
Not sure what you mean here.
Shannon Barber
Shannon Barber
Instead of doing the bit management yourself, make the compiler do it for you.


struct mine
{
unsigned int color:8;
unsigned int type:8;
unsigned int square:8;
unsigned int dummy:8;
};



Debug the code, step through it and see what it does differently. It may have something to do with the sign-extention when the flag is promoted to an int (assuming attackable is an int).
The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara
Jan Wassenberg
Jan Wassenberg
>> I think you''d be better off with bitboards
quote:
I have written several chess programs. Currently I am just playing around with different board representations. I''ve done several different kinds of bitboard engines in the past.

aight.

quote:

The reason I started in on using 8-bit variables is to make it easier to copy the whole structure. In a chess program, you will do a lot of piece copying from square to square, move copying everytime you make a move to back it up, and so on. I thought the 32-bit structure with 8-bit fields would be more efficient in the copying aspect. 1 copy vs. 4. So what would you recommend here?


So your board is an array of this struct? I''d just use ints, encoding the color in the MSB (or whatever). Copying is simple+fast, and it''s no bigger than the struct (smaller if the compiler padded it).

>> BTW, watch for [ i ] in code
> Not sure what you mean here.
Your post suddenly goes italic after a certain array index
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3
cgoat
cgoat
Look at the following:


    
int main(int argc, char *argv[])
{
unsigned char x = 0;
char y = -17;

x+=y;

printf("%d\n", x);

return 0;
}


On g++ 3.2, this yields an x value of 239. If you add 128 to that, you're past 256. The reason your second one works, is because you use an int, which properly stores the negative value.

EDIT: In other words, "square" should be a char, not unsigned.

[edited by - cgoat on March 13, 2003 11:31:20 AM]
ToohrVyk
ToohrVyk

 
void RayTrace (int iterations, char offset, unsigned char bit) {
{
int i;
unsigned char square;
for (i = square = 0; i < iterations; i++) {
square += offset;
attackable[square + 128] |= bit;
step[square + 128] = offset;
}
}



Ok I see it now... The error lies in the attackable[square + 128] line. The point is, square + 128 adds the value of 128 to the value of square, and returns a value the same size as square. This is why, if square is an int, it will work, and if square is a char, it won''t (8 bits is too small to actually contain more than 256 values).

Two ways to work around this are :
1° Revert square to an int
2° Use attackable[(int)square + 128]

(this will convert square to a 32bit int value, so the sum will also be done over 32 bits).

ToohrVyk
-------------
Extatica - a free 3d game engine
Available soon!
Click here to learn more
cgoat
cgoat
Actually, the problem lies in passing a negative value for offset. He''d have the same problem if "sq" was an unsigned int. If you take 0 and add -17 to it, you get -17. But cast -17 to an unsigned char and you get 239. Cast it to an unsigned int and you get a very large number. Thus, taking that unsigned value and adding 128 to it gives an out of range index.
Russell
Russell
quote:
Original post by Jan Wassenberg
So your board is an array of this struct? I''d just use ints, encoding the color in the MSB (or whatever). Copying is simple+fast, and it''s no bigger than the struct (smaller if the compiler padded it).
This is what I had done previously. I ran into a problem though. I used the least significant bit as the ''color'' bit of the piece on the square. This made it nice and easy to use the color as the index into an array. My problem was that if I had a loop such as this:

#define WHITE 0
#define BLACK 1

#define Color(x) ((x) & 1)

if (Color(board[square]) == WHITE) {
// do stuff
}

The problem is that an empty square (which I had as 0) would also turn up as a WHITE square. If I used some other value for ''empty'' such as -1, then it will think the color of an emty square is black. That meant I had to use more than one bit, so if I''m going to use more bits, I could do as I''m doing now, get rid of an AND to find out the color, and still keep the struct at 32-bits for fast copying. I *could* have done an extra test to make sure the square wasn''t empty before I tested its color, but why do that when I can do it this way, avoid the test, and get rid of an AND?

Anyway, I started writing an amazons program last night. It''s way easier than chess. In a little over an hour''s time of coding, I have a working program.

Thanks for everyone''s help. I think I just need to play around with mixing variables and get a better grasp of it, and be more careful when I mix them.
Jan Wassenberg
Jan Wassenberg
Sorry for the late reply - missed your post.

quote:
This is what I had done previously. I ran into a problem though. I used the least significant bit as the ''color'' bit of the piece on the square.

I think checking which piece is on the square is more frequent; storing color in the LSB makes this a bit harder.

> This made it nice and easy to use the color as the index into an array.
Yes. Usually, though, I figure you''d have a color variable that''s extracted once from the square (e.g. with shr), making this moot.

quote:
My problem was that if I had a loop such as this:
#define Color(x) ((x) & 1)
if (Color(board[square]) == WHITE) {}

Alternate way to get square color, without a memory hit: extract row and column (you probably have these already, if not, it''s a mov, and, shr), add them, check if it''s even.

quote:
The problem is that an empty square (which I had as 0) would also turn up as a WHITE square.

I don''t see the problem. Do you need a test for non-empty, white squares? In that case, empty := piece # 0; check if color == white and piece != 0.
This is easier with a bitboard: you can easily enumerate all non-empty squares, and you just check whether it''s white.

quote:
If I used some other value for ''empty'' such as -1, then it will think the color of an emty square is black. That meant I had to use more than one bit, so if I''m going to use more bits, I could do as I''m doing now, get rid of an AND to find out the color, and still keep the struct at 32-bits for fast copying.

An ''empty'' bit is redundant; you can encode this in the piece field as above. Also, cmp [color_var], 1 is no faster / smaller than test [square], COLOR_BIT.

quote:
I *could* have done an extra test to make sure the square wasn''t empty before I tested its color, but why do that when I can do it this way, avoid the test, and get rid of an AND?

I wonder why you need this information; also, you''re not getting rid of an and, you''re replacing it with a cmp.

quote:
Thanks for everyone''s help. I think I just need to play around with mixing variables and get a better grasp of it, and be more careful when I mix them.

Simple rule of thumb: unless you''re squeezed for space, use int in place of char.
E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.