Breakout, the game the course builds in HiSoft C, running on the Tatung Einstein: coloured brick rows, the bat and the ball on a black court
← Back to Courses
module
18

Structures

Introduction

A ball has a position and a direction: four numbers that belong together and travel together. In the sections so far you would have declared bx, by, bdx, bdy and passed all four to every function that touched the ball. A structure is those four under one name, ball, with the parts reached as one.x and one.dy, and one address to pass to a function. Breakout has a ball, a bat and a game, and each is a structure. This compiler does most of what you would expect with them, refuses two things politely, and has one rule of its own that will bite if you do not know it.

Declaring One

typedef struct {
    int x, y;
    int dx, dy;
} ball;

This says what a ball is: two ints for where it is and two for which way it is going. typedef (S10) makes ball a type name, so that from here on ball one; declares a variable of that type exactly as int n; does. The names inside are the members, and one.x is the x of one: read it, assign to it, use it in a sum, like any int.

sizeof(ball) is 8: four ints, packed one after another. A bat of two ints is 4. You may also write the older form, struct ball { ... }; and then struct ball one;, which means the same; the course uses typedef because a one-word type name reads better and is what the cast in S16 needs.

Structures may contain arrays and other structures - a player with a char name[10] and an int score is 12 bytes - and you may have arrays of structures, ball balls[3], indexed as balls[i].x.

Giving One To A Function

S13 said arguments are copies, and S16 said to pass an address when a function must change the caller's variable. Structures go by address always, because this compiler will not copy one into a function: write show(p) point p; and it says ERROR 45 ... illegal parameter type. So:

void step(b) ball *b;
{
    b->x += b->dx;
    b->y += b->dy;
}

b is a pointer to a ball, and b->x is the x of the ball it points at - the arrow is . for use through a pointer, and it is what you write inside any function that receives a structure. The caller passes &one. After step(&one) twice, a ball at (20,12) moving (1,-1) is at (22,10), and it was one itself that moved.

A pointer to a structure works the same way outside a function: p = &one; p->dy = 1; and one.dy is 1.

What It Will Not Do

No assignment of whole structures. b = a on two points is ERROR 32 ... bad type combination. Copy the members one at a time, or - if the structure is big - S16's blt from the library, which the compiler's own RESTRICTION message mentions.

No structures by value, as above: neither as an argument nor, therefore, as a function's result. Pointers, always.

No initialisers on structures of ints. This one is not refused; it is worse. point here = { 20, 12 }; compiles, and here.x is 3092 and here.y is 0. The compiler put each value into one byte, so 20 and 12 landed in the two bytes of x, as 20 + 12 × 256. (With char members it comes out right, for the same reason.) Set the members by assignment, always; the course never writes { } after a structure.

One Name, One Member

Here is the rule. A member name may be used in only one structure in the whole program. If ball has an x, then bat may not have an x - and the compiler will not necessarily tell you.

Declare bat as { int x, width; } after ball has taken x, and sizeof(bat) is 2, not 4; set paddle.x = 18 and paddle.width = 5 and both read back as 5, because both are the same two bytes. The reused name was silently skipped. Reuse a name at a different position - x as the second member of bat - and the compiler does say something: ERROR 21 ... duplicate declaration - storage class mismatch at the line.

Older C compilers kept all the member names in one table with an offset each, and this is one of them. The habit that avoids it is to give every structure's members names that say which structure they belong to, or simply to check: the listing calls the bat's members left and width, and the game will call the score's members score and lives, and nothing is called x except the ball's x.

The Code

#include STDIO.H

typedef struct {
    int x, y;
    int dx, dy;
} ball;

typedef struct {
    int left, width;
} bat;

void show(b) ball *b;
{
    printf("ball at (%d,%d) moving (%d,%d)\n", b->x, b->y, b->dx, b->dy);
}

void step(b) ball *b;
{
    b->x += b->dx;
    b->y += b->dy;
}

main()
{
    ball one;
    bat paddle;
    ball *p;
    one.x = 20;
    one.y = 12;
    one.dx = 1;
    one.dy = -1;
    show(&one);
    step(&one);
    step(&one);
    show(&one);
    p = &one;
    p->dy = 1;
    printf("dy is now %d\n", one.dy);
    paddle.left = 18;
    paddle.width = 5;
    printf("bat from %d to %d\n", paddle.left, paddle.left + paddle.width - 1);
    printf("%d bytes for a ball, %d for a bat\n", sizeof(ball), sizeof(bat));
}

#include ?STDIO.LIB?

show and step are void because they are called for what they do; each takes a ball *. The printf in show is longer than the screen is wide and wraps in ED80; that is fine.

Starting from

Typed as STRUCT.C; compiled and run.

What you should see

ball at (20,12) moving (1,-1)
ball at (22,10) moving (1,-1)
dy is now 1
bat from 18 to 22
8 bytes for a ball, 4 for a bat

Change One Thing

  • Rename bat's left to x. Compile and run. What are the bat's numbers and its size now, and did the compiler say anything?
  • Declare ball two; and write two = one; after the first show. What does the compiler say?
  • Change void step(b) ball *b; to void step(b) ball b; and the calls to step(one). Which message, and at which line?

Exercises

18.1 Write taxi(a, b) that takes the addresses of two points and returns the distance between them counted in squares (the sum of the differences in x and y, ignoring sign - the library's abs helps).

18.2 Declare a global ball balls[3], start them at different places with different speeds, step all three three times in a loop, and print where they are.

18.3 Declare a player with a char name[10] and an int score, fill it with strcpy and an assignment, and print it and its size.

Worked solutions are in Appendix II.

When It Goes Wrong

SymptomCause
bad type combination at a = bStructures cannot be assigned. Copy the members.
illegal parameter typeA structure passed by value. Pass &it and declare type *p.
A structure's members hold absurd values you never assignedIt was initialised with { }. Assign them.
Two members of a structure always hold the same value, or sizeof is too smallA member name reused from another structure. Rename it.
duplicate declaration - storage class mismatch at a memberA member name reused from another structure, at a different position. Rename it.
need a type name at sizeof(one)sizeof takes the type: sizeof(ball).
undefined variable for a member. used through a pointer, or -> on a structure.

Summary

typedef struct { members } name; then name var;, var.member, and through a pointer p->member. Pass structures by address, never by value; never assign one to another; never initialise one with { }. Every member name is unique across the whole program. sizeof(name) gives the size.

Next

S19, The Preprocessor - #define for the court's size and the characters on it, #include for a file of your own, and what this compiler's preprocessor leaves out.

Get the Newsletter

New guides, disk images and community finds, roughly once a quarter. No spam, we promise, this isn't Tatung's marketing department.
Your subscription could not be saved. Please try again.
Your subscription has been successful.

Newsletter

Subscribe to our newsletter and stay updated.