Bit Fields

I l @ ve RuBoard

Bit Fields

The second method of manipulating bits is to use a bit field , which is just a set of neighboring bits within an unsigned int . A bit field is set up with a structure declaration that labels each field and determines its width. For instance, the following declaration sets up four 1-bit fields:

 struct   {          unsigned int autfd   : 1;          unsigned int bldfc   : 1;          unsigned int undln   : 1;          unsigned int itals   : 1; } prnt; 

This definition causes prnt to contain four 1-bit fields. Now you can use the usual structure membership operator to assign values to individual fields:

 prnt.itals = 0; prnt.undln = 1; 

Because each of these particular fields is just 1 bit, 1 and are the only values you can use for assignment. The variable prnt is stored in an int - sized memory cell , but only 4 bits are used in this example.

Fields aren't limited to 1-bit sizes. You can also do this:

 struct {        unsigned int code1 : 2;        unsigned int code2 : 2;        unsigned int code3 : 8; } prcode; 

This code creates two 2-bit fields and one 8-bit field. You can now make assignments such as the following:

 prcode.code1 = 0; prcode.code2 = 3; prcode.code3 = 102; 

Just make sure the value doesn't exceed the capacity of the field.

What if the total number of bits you declare exceeds the size of an int ? Then the next int storage location is used. A single field is not allowed to overlap the boundary between two int s. The compiler automatically shifts an overlapping field definition so that the field is aligned with the int boundary. When this occurs, it leaves an unnamed hole in the first int .

You can "pad" a field structure with unnamed holes by using unnamed field widths. Using an unnamed field width of 0 forces the next field to align with the next integer:

 struct {     unsigned int field1 : 1;     unsigned int        : 2;     unsigned int field1 : 1;     unsigned int        : 0;     unsigned int field1 : 1; } stuff; 

Here, there is a 2-bit gap between stuff.field1 and stuff.field2 , and stuff.field3 is stored in the next int .

One important machine dependency is the order in which fields are placed into an int . On some machines the order is left to right, and on others it is right to left. Also, machines differ in the location of boundaries between fields. For these reasons, bit fields tend not to be very portable. Typically, however, they are used for nonportable purposes, such as putting data in the exact form used by a particular hardware device.

Bit-Field Example

Often bit fields are used as a more compact way of storing data. Suppose, for example, you decided to represent the properties of an onscreen box. Let's keep the graphics simple and suppose the box has the following properties:

  • The box is opaque or transparent.

  • The fill color is selected from the following palette of colors: black, red, green, yellow, blue, magenta , cyan, or white.

  • The border can be shown or hidden.

  • The border color is selected from the same palette used for the fill color.

  • The border can use one of three line styles: solid, dotted , or dashed.

You could use a separate variable or a full-sized structure member for each property, but that is a bit wasteful of bits. For example, you need only a single bit to indicate whether the box is opaque or transparent, and you need only a single bit to indicate if the border is shown or hidden. The eight possible color values can be represented by the eight possible values of a 3-bit unit, and a 2-bit unit is more than enough to represent the three possible border styles. A total of 10 bits, then, is enough to represent the possible settings for all five properties.

Here's one possible representation of the information; the struct box_props declaration uses padding to place the fill-related information in one byte and the border- related information in a second byte.

 struct box_props {     unsigned int opaque         : 1;     unsigned int fill_color     : 3;     unsigned int                : 4;     unsigned int show_border    : 1;     unsigned int border_color   : 3;     unsigned int border_style   : 2;     unsigned int                : 2; }; 

The padding brings the structure size up to 16 bits. Without padding, the structure would be 10 bits. However, because memory allocation is usually aligned with the beginning of bytes or words, creating a 10-bit structure most likely would not save any memory compared to a 16-bit structure.

You can use a value of 1 for the opaque member to indicate that the box is opaque and a value to indicate transparency. You can do the same for the show_border member. For colors, you can use a simple RGB ( red-green-blue ) representation. These are the primary colors for mixing light. A monitor blends red, green, and blue pixels to reproduce different colors. In the early days of computer color, each pixel could be either on or off, so you could use one bit to represent the intensity of each of the three binary colors. The usual order is for the left bit to represent blue intensity, the middle bit green intensity, and the right bit red intensity. Table 15.3 shows the eight possible combinations. They can be used as values for the fill_color and border_color members . Finally, you can choose to let 0, 1, and 2 represent the solid, dotted, and dashed styles; they can be used as values for the border_style member.

Table  15.3. Simple color representation.
Bit Pattern Decimal Color
000 Black
001 1 Red
010 2 Green
011 3 Yellow
100 4 Blue
101 5 Magenta
110 6 Cyan
111 7 White

Listing 15.3 uses the box_props structure in a simple example. It uses #define to create symbolic constants for the possible member values. Note that the primary colors are represented by a single bit being on. The other colors can be represented by combinations of the primary colors. For example, magenta consists of the blue bit and the red bit being on, so it can be represented by the combination BLUE RED .

Listing 15.3 The fields.c program.
 /* fields.c -- define and use fields */ #include <stdio.h> /* opaque and show */ #define YES     1 #define NO      0 /* line styles     */ #define SOLID   0 #define DOTTED  1 #define DASHED  2 /* primary colors  */ #define BLUE    4 #define GREEN   2 #define RED     1 /* mixed colors    */ #define BLACK   0 #define YELLOW  (RED  GREEN) #define MAGENTA (RED  BLUE) #define CYAN    (GREEN  BLUE) #define WHITE   (RED  GREEN  BLUE) struct box_props {     unsigned int opaque         : 1;     unsigned int fill_color     : 3;     unsigned int                : 4;     unsigned int show_border    : 1;     unsigned int border_color   : 3;     unsigned int border_style   : 2;     unsigned int                : 2;  }; const char * colors[8] = {"black", "red", "green", "yellow",             "blue", "magenta", "cyan", "white"}; int main(void) {     /* create and initialize box_props stucture */     struct box_props box = {YES, YELLOW , YES, GREEN, DASHED};     printf("Box is %s.\n",         box.opaque == YES? "opaque": "transparent");     printf ("The border style is ");     switch(box.border_style)     {         case SOLID  : printf("solid.\n"); break;         case DOTTED : printf("dotted.\n"); break;         case DASHED : printf("dashed.\n"); break;         default     : printf("unknown type.\n");     }     printf("The fill color is %s.\n", colors[box.fill_color]);     printf("The border color is %s.\n", colors[box.border_color]);     box.opaque = NO;     box.fill_color = WHITE;     box.border_color = MAGENTA;     box.border_style = SOLID;     printf("After changes, box is %s.\n",         box.opaque == YES? "opaque": "transparent");     printf ("The border style is ");     switch(box.border_style)     {         case SOLID  : printf("solid.\n"); break;         case DOTTED : printf("dotted.\n"); break;         case DASHED : printf("dashed.\n"); break;         default     : printf("unknown type.\n");     }     printf("The fill color is %s.\n", colors[box.fill_color]);     printf("The border color is %s.\n", colors[box.border_color]);    return 0; } 

Here is the output:

 Box is opaque. The border style is dashed. The fill color is yellow. The border color is green. After changes, box is transparent. The border style is solid. The fill color is white. The border color is magenta. 

There are some points to note. First, you can initialize a bit-field structure by using the same syntax regular structures use:

 struct box_props box = {YES, YELLOW , YES, GREEN, DASHED}; 

Similarly, you can assign to bit-field members:

 box.fill_color = WHITE; 

Also, you can use a bit-field member as the value expression for a switch statement. You can even use a bit-field member as an array index:

 printf("The fill color is %s.\n", colors[box.fill_color]); 

Notice that the colors array was defined so that each index value corresponds to a string representing the name of the color having the index value as its numeric color value. For example, an index of 1 corresponds to the string "red" , and red has the color value of 1 .

Bit Fields and Bitwise Operators

Bit fields and bitwise operators are two alternative approaches to the same type of programming problem. That is, often you could use either approach. For instance, the previous example used a 2-byte structure to hold information about a graphics box, or you could use a 2-byte integer value, such as the typical unsigned short , to hold the same information. Then, instead of using structure member notation to access different parts , you could use the bitwise operators for that purpose. Typically, this is a bit more awkward to do. Let's look at an example that takes both approaches. (The reason for taking both approaches is to illustrate the differences, not to suggest that taking both approaches simultaneously is a good idea!)

You can use a union as a means of combining the structure approach with the bitwise approach. Given the existing declaration of the struct box_props type, you can declare the following union:

 union Views     /* look at data as struct or as unsigned short */ {     struct box_props st_view;     unsigned short   sh_view; }; 

On many systems, including ours, an unsigned short and a box_props structure both occupy 16 bits of memory. With this union, you can use the st_view member to look at that memory as a structure or use the sh_view member to look at the same block of memory as an unsigned short . Which bit fields of the structure correspond to which bits in the unsigned short ? That depends on the implementation and the hardware. On an IBM PC using Microsoft Visual C/C++ 5.0, structures are loaded into memory from the low-bit end to the high-bit end of a byte. That is, the first bit field in the structure goes into bit 0 of the word (see Figure 15.3).

Figure 15.3. A union as an integer and as a structure.
graphics/15fig03.jpg

Listing 15.4 uses the Views union to let you compare the bit field and bitwise approaches. In it, box is a Views union, so box.st_view is a box_prop structure using bit fields, and box.sh_view is the same data viewed as an unsigned short . The program also uses a shtobs() function, based on the itobs() function defined earlier in this chapter, to display the data as a binary string so that you can see which bits are on and which are off.

Listing 15.4 The dual.c program.
 /* dual.c -- bit fields and bitwise operators */ #include <stdio.h> /* BIT-FIELD CONSTANTS */ /* opaque and show */ #define YES     1 #define NO      0 /* line styles     */ #define SOLID   0 #define DOTTED  1 #define DASHED  2 /* primary colors  */ #define BLUE    4 #define GREEN   2 #define RED     1 /* mixed colors    */ #define BLACK   0 #define YELLOW  (RED  GREEN) #define MAGENTA (RED  BLUE) #define CYAN    (GREEN  BLUE) #define WHITE   (RED  GREEN  BLUE) /* BITWISE CONSTANTS   */ #define OPAQUE          1 #define FILL_BLUE       8 #define FILL_GREEN      4 #define FILL_RED        2 #define FILL_MASK       14 #define BORDER          256 #define BORDER_BLUE     2048 #define BORDER_GREEN    1024 #define BORDER_RED      512 #define BORDER_MASK     3584 #define B_DOTTED        4096 #define B_DASHED        9192 #define STYLE_MASK     13288 struct box_props {     unsigned int opaque         : 1;     unsigned int fill_color     : 3;     unsigned int                : 4;     unsigned int show_border    : 1;     unsigned int border_color   : 3;     unsigned int border_style   : 2;     unsigned int                : 2;  }; const char * colors[8] = {"black", "red", "green", "yellow",             "blue", "magenta", "cyan", "white"}; char * shtobs(int n, char * ps); /* display short as binary string */ int main(void) {     union Views     /* look at data as struct or as unsigned short */     {         struct box_props st_view;         unsigned short   sh_view;     };     /* create Views object, initialize struct box view */     union Views box = {{YES, YELLOW , YES, GREEN, DASHED}};     char bin_str[8 * sizeof(int) + 1];     printf("Box is %s.\n",         box.st_view.opaque == YES? "opaque": "transparent");     printf ("The border style is ");     switch(box.st_view.border_style)     {         case SOLID  : printf("solid.\n"); break;         case DOTTED : printf("dotted.\n"); break;         case DASHED : printf("dashed.\n"); break;         default     : printf("unknown type.\n");     }     printf("The fill color is %s.\n",         colors[box.st_view.fill_color]);     printf("The border color is %s.\n",         colors[box.st_view.border_color]);     printf("bits are %s\n",         shtobs(box.sh_view,bin_str));     box.sh_view &= ~FILL_MASK;          /* clear fill bits */     box.sh_view = (FILL_BLUE  FILL_GREEN); /* reset fill */     box.sh_view ^= OPAQUE;               /* toggle opacity */     printf("After changes, box is %s.\n",         box.st_view.opaque == YES? "opaque": "transparent");     printf("The fill color is %s.\n",         colors[box.st_view.fill_color]);     printf("The border color is %s.\n",         colors[(box.sh_view >> 9) & 07]);     printf("bits are %s\n",         shtobs(box.sh_view,bin_str));    return 0; } /* convert short to binary string */ char * shtobs(int n, char * ps) {    int i;    static int size = 8 * sizeof(short);    for (i = size - 1; i >= 0; i--, n >>= 1)         ps[i] = (01 & n) + '0';    ps[size] = ' 
 /* dual.c -- bit fields and bitwise operators */ #include <stdio.h> /* BIT-FIELD CONSTANTS */ /* opaque and show */ #define YES 1 #define NO 0 /* line styles */ #define SOLID 0 #define DOTTED 1 #define DASHED 2 /* primary colors */ #define BLUE 4 #define GREEN 2 #define RED 1 /* mixed colors */ #define BLACK 0 #define YELLOW (RED  GREEN) #define MAGENTA (RED  BLUE) #define CYAN (GREEN  BLUE) #define WHITE (RED  GREEN  BLUE) /* BITWISE CONSTANTS */ #define OPAQUE 1 #define FILL_BLUE 8 #define FILL_GREEN 4 #define FILL_RED 2 #define FILL_MASK 14 #define BORDER 256 #define BORDER_BLUE 2048 #define BORDER_GREEN 1024 #define BORDER_RED 512 #define BORDER_MASK 3584 #define B_DOTTED 4096 #define B_DASHED 9192 #define STYLE_MASK 13288 struct box_props { unsigned int opaque : 1; unsigned int fill_color : 3; unsigned int : 4; unsigned int show_border : 1; unsigned int border_color : 3; unsigned int border_style : 2; unsigned int : 2; }; const char * colors[8] = {"black", "red", "green", "yellow", "blue", "magenta", "cyan", "white"}; char * shtobs(int n, char * ps); /* display short as binary string */ int main(void) { union Views /* look at data as struct or as unsigned short */ { struct box_props st_view; unsigned short sh_view; }; /* create Views object, initialize struct box view */ union Views box = {{YES, YELLOW , YES, GREEN, DASHED}}; char bin_str[8 * sizeof(int) + 1]; printf("Box is %s.\n", box.st_view.opaque == YES? "opaque": "transparent"); printf ("The border style is "); switch(box.st_view.border_style) { case SOLID : printf("solid.\n"); break; case DOTTED : printf("dotted.\n"); break; case DASHED : printf("dashed.\n"); break; default : printf("unknown type.\n"); } printf("The fill color is %s.\n", colors[box.st_view.fill_color]); printf("The border color is %s.\n", colors[box.st_view.border_color]); printf("bits are %s\n", shtobs(box.sh_view,bin_str)); box.sh_view &= ~FILL_MASK; /* clear fill bits */ box.sh_view = (FILL_BLUE  FILL_GREEN); /* reset fill */ box.sh_view ^= OPAQUE; /* toggle opacity */ printf("After changes, box is %s.\n", box.st_view.opaque == YES? "opaque": "transparent"); printf("The fill color is %s.\n", colors[box.st_view.fill_color]); printf("The border color is %s.\n", colors[(box.sh_view >> 9) & 07]); printf("bits are %s\n", shtobs(box.sh_view,bin_str)); return 0; } /* convert short to binary string */ char * shtobs(int n, char * ps) { int i; static int size = 8 * sizeof(short); for (i = size - 1; i >= 0; i--, n >>= 1) ps[i] = (01 & n) + '0'; ps[size] = '\0'; return ps; } 
'; return ps; }

Here is the output:

 Box is opaque. The border style is dashed. The fill color is yellow. The border color is green. bits are 0010010100000111 After changes, box is transparent. The fill color is cyan. The border color is green. bits are 0010010100001100 

There are several points to discuss. One difference between the bit-field and bitwise views is that the bitwise view needs positional information. For example, we've used BLUE to represent the color blue. This constant has the numerical value of 4 . But, because of the way the data is arranged in the structure, the actual bit holding the blue setting for the fill color is bit 3 (remember, numbering starts at 0), and the bit holding the blue setting for the border color is bit 11. Therefore, the program defines some new constants:

 #define FILL_BLUE       8 #define BORDER_BLUE     2048 

Here, 8 is the value if just bit 3 is set to 1, and 2048 is the value if just bit 11 is set to 1. You can use the first constant to set the blue bit for the fill color and the second constant to set the blue bit for the border color.

Note that using bitwise operators to change settings is more complicated. For example, consider setting the fill color to cyan. It is not enough just to turn the blue bit and the green bit on:

 box.sh_view = (FILL_BLUE  FILL_GREEN); /* reset fill */ 

The problem is that the color also depends on the red bit setting. If that bit is already set (as it is for the color yellow), then this code leaves the red bit set and sets the blue and green bits, resulting in the color white. The simplest way around this problem is to turn all the color bits off first, before setting the new values. That is why the program used the following code:

 box.sh_view &= ~FILL_MASK;          /* clear fill bits */ box.sh_view = (FILL_BLUE  FILL_GREEN); /* reset fill */ 

In a case like this, the bit-field equivalent is simpler:

 box.st_view.fill_color = CYAN;  /*bit-field equivalent */ 

You don't need to clear the bits first. Also, with the bit-field members, you can use the same color values for the border as for the fill, but you need to use different values (values reflecting the actual bit positions ) for the bitwise operator approach.

Next, compare the following two print statements:

 printf("The border color is %s.\n",         colors[box.st_view.border_color]); printf("The border color is %s.\n",         colors[(box.sh_view >> 9) & 07]); 

In the first statement, the expression box.st_view.border_color has a value in the range 0 “7, so it can be used as an index for the colors array. Getting the same information with bitwise operators is more complex. One approach is to use box.sh_view >> 9 to right-shift the border-color bits to the rightmost position in the value (bits 0 “2). Then combine this value with a mask of 07 so that all bits but the rightmost 3 are turned off. Then what is left is in the range 0 “7 and can be used as an index for the colors array.

Caution

The correspondence between bit fields and bit positions is implementation dependent. For example, running Listing 15.4 on a Macintosh produces the following output:

 Box is opaque. The border style is dashed. The fill color is yellow. The border color is green. bits are 1011000010101000 After changes, box is opaque. The fill color is yellow. The border color is black. bits are 1011000010101101 

The code changed the same bits as before, but the Macintosh loads the structure into memory differently. In particular, it loads the first bit field into the highest-order bit instead of the lowest -order bit. Therefore, the assumptions that Listing 15.4 makes about the location of bits is incorrect for the Macintosh, and using bitwise operators to change the opacity and fill color settings alters the wrong bits.


I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net