C - From The Top (part 10)

As you can by now appreciate, C is not only rather easy to understand, but also quite jolly to program under. This is not by mistake either!

On to a practical problem. A while back, I asked you to design and write a program which would store names, addresses, phone numbers and the like and, quite probably, you would have had as global variables something akin to

char name[100], addr1[100], addr2[100] etc.

and for the input routine, you'd have a pile of either gets(var[no]); or scanf("%s",& var[no]); commands. While this gets the job done, it does make for problems in debugging and it generates extra lines of code. This can all be done away with by the use of the struct command.

Remember, there is nothing to say that name[0] is related to addr[0]. The struct defines a new compound data type which holds all these pieces of information together.

Format

struct name {list} name2,*ptr

where name is a tag name (the type name for the structure) and name2 is known as the variable list. This is the part you use when accessing the structure. list is a list of variables associated with this structure.

name2 and *ptr aren't obligatory.

Unlike the Basic counterpart, DIM, a structure can have any of the recognised variable types inside it.

For instance, in the names and addresses program, a suitable structure might look like this:

struct entry
{
 char name[60];
 char address1[60];
 char address2[60];
 char postcode[10];
 int telephone;
};

This defines a structure type called entry, which will contain four strings and an int. Note that it only defines the structure. We haven't declared any variables to be of this type. To do this, we do the following:

struct entry person;

This declares the variable person to be an 'entry' struct.

As a struct is a grouping of several bits of data, we need to be able to access them separately, e.g.

strcpy (person.name,"Emergency services");
person.telephone=999;

The name of the variable is followed by a full stop and the name of the part of the struct we wish to access. We do exactly the same when wanting to output the data from the struct again:

printf("Phone no. :%d\n",person.telephone);

We have seen how to declare the format of a struct and how to create a variable of that type, and as we've already seen, in C we can combine them both by listing the variables after the struct declaration:

struct entry
{
 char name[60];
 char address1[60];
 char address2[60];
 char postcode[10];
 int telephone;
}
person, people[50], *person_ptr;

This declares a variable person of type struct entry, an array called people, each element being a struct, and finally a pointer to this type of struct, *person_ptr.

This can be fixed by the use of a pointer. The pointer should be positioned after the variable type list name:

} details[100],*pointer;

At the start of the input routine, you should assign the pointer to equal the address of details. Using the pointer to write to a particular part of the structure requires a slightly different syntax. Whereas before you would have:

gets (var[no].part);

You would now have

gets (pointer->part);

The important part is the ->. pointer is equal to the start address of the variable list name. What is happening here is that you are accessing the area assigned to var[no].part in memory.

While this may not seem to be that important, think along these lines: in your average database, you may have 3000 actual records, each containing eight fields (called a to h). Each field works out to have an average of 60 characters in it. This would work out to mean that the structure itself would have a size of 5.5Mb (3000 × 8 × 60 × 4 - where 4 is the number of bytes set aside for a char or int). Could you imagine attempting to write code which asked you to input record 2999, field e? The nightmare would begin!

The good news is that pointer arithmetic on structs is no problem. Take the following bit of code:

struct entry
{
 char name[60];
 char address[60];
 int telephone;
};
int main(void)
{
 struct entry person,people[50], *person_ptr;
 strcpy(people[40].name,"Emergency services");
 people[40].telephone=999;
 person_ptr=&people[0];
 printf("%s %d\n",person_ptr[40].name, person_ptr[40].telephone);
}
person_ptr has been set to be pointing at the base of the people array, and person_ptr can then be
passed to a function. The printf line shows how the data can be accessed from within the function.
All you do is put the number of the array item you want in square brackets.

Structures are not restricted to one variable list, as for loops; they can also be embedded. It is done like this:

struct listone
{
 char number1[10];
 int something;
}
struct listtwo
{
 char xyz[10];
 char abc[10];
 struct listone list;
} somename, *pointer;

This sort of embedding would be useful for the likes of a library system or, in a more practical way, for part of a wimp JPEG routine (I'll be getting onto the wimp in the not too distant future).

Writing to this embedded structure is the same as writing to any other part of the structure

pointer -> somename.list.number1
list is the name given in the somename structure for the first (listone) structure.

As a summary then, structures can also be tested using if statements:

if (strcmp(pointer-> abc,"Fred"))
{
 do something
}

or, if not used with a pointer, the line becomes:

if (strcmp(somename.abc,"Fred"))
{
 do something
}

Unions

A union is a single piece of memory shared by two or more variables. These variables can be of different types, but only one variable from it can be used at any one time. To ensure that the stack isn't corrupted, the size of the union will be determined by its largest member.

Format

union tag {type members} var-name;

The type members can be any of the recognised variable types, and you can have as many of them as you wish! For example,

union name {
 int number;
 char fred[2];
 double number2;
} listname;

This would produce (in memory) a block like this:

Accessing the union is the same as for a structure, e.g. the above would be:

listname.number2=123.456;

OK, so how would we really use them? One good example is when you are creating an icon in a window (that's for the WIMP - which is coming - but for now it will show how it is used).

union alt
{
 char text[12];
 struct
 {
  int *pointer_text;
  int *pointer_validation;
  int text_len;
 } indirected;
};
typedef struct
{
 int window_handle;
 int min_x_bound_box;
 int min_y_bound_box;
 int max_x_bound_box;
 int max_y_bound_box;
 int icon_flags;
 union alt redirect;
} icon;

The union alt is saying (effectively) that either we have 12 characters of text or three ints held within the struct.

This is then used within the typedef struct to say that the seventh element (the union statement) is either text or another structure.

A final type of structure is the bit field structure. This is where you are able to access, by name, one or more bits from a byte or word (a word being four bits).

Format

type name : size in bits

type has to be either int or unsigned. You can make it signed, but this may not always work. The colon separates the name from the size in bits for this particular name.

As you are also only dealing with bits, this should also require far less memory than a normal structure. For instance, this fragment could be used for storing CD information:

struct cd_info {
 unsigned length : 1
 unsigned cd_type : 1
 unsigned price : 4
 unsigned status : 1
} cd[large_number];

(An explanation of the numbers: all numbers are in binary, therefore 4 will give four bits, meaning that any value from 0 to 15 can be stored.)

The down point to the use of a bit field structure is that, in the above snippet, you can't say that the CD is £12.99 - it has to be 13 pounds - and also that you need to use a system of codes. For instance, length would need 0 for 74 minutes, and 1 for 80.

The bit field also doesn't need to add up to eight as the compiler will try and fit it into the smallest block of memory possible.

That said, bit fields can be quite useful as you can still use other types of members within the structure (the structure is still a structure after all!). If I took the above snippet and added

char title [30];
char artist [40];

the amount of memory required for the structure would have grown, but not as much as if I had defined the other variables within the structure as doubles, ints and suchlike.

Accessing the bit field structure is the same as accessing any other type of structure.

Saving and loading a structure

Saving and loading the contents of a structure is the same as saving and loading anything else. It can be saved as a text or binary file (though binary would be better here).

There is one slight difference. Even though you may have said that you want a structure to have (say) 3000 elements, only 250 may have been used. There seems little point in outputting the other 2750 to disc. (Of course, the same would equally apply to a normal array - it's not a problem specific to structs!)

To get around this, the following can be used:

if (fwrite(&current,sizeof current,1,fileptr)!=1)

where current is an int in which we have stored the index of the highest element of the array which is actually in use.

if (fwrite(pointer,sizeof struct_name,1,filename)!=1)

This writes the structure to disc (structure_ name is the name of the struct you're saving, e.g. entry).

Or you could use:

if (fread(counter,sizeof counter,1,filename)!=1)

This reads in the number of the counter at the time of saving. It means that the next entry can be made without overwriting the last.

if (fread(entry,sizeof entry,1,filename)!=1)

should read the structure back in.

A practical example to sum this up...

struct name details[100],*ptr;
int top; /*stores number of entries use*/
:
pointer=&details[0];
top=50;
:
if(fwrite(&top,sizeof top,1,fileptr)!=1)
{
 printf("Write error\n");
}
if(fwrite(pointer,sizeof (struct name),top,fileptr)!=top)
{
 printf("Write error\n");
}

and for reading

if (fread(&top,sizeof top,1,fileptr)!=1)
{
 printf("Error reading the counter\n");
}
if (fread(pointer,sizeof (struct name),1,fileptr)!=1)
{
 printf("Error reading in\n");
}

Next time, I'll be covering bit-wise operations, typedef and enum. Until then, here's a task for you to try.

Taking your original names and addresses program, convert it so that it uses structures and pointers to store the information. Compile the program - you should find that it is smaller than the original program (this may not always be the case - it depends on the size of your original code).


Source: Archive Magazine 13.10
Publication: Archive Magazine
Contributor: Paul Johnson