All about structures


Match word(s).

If you have any questions or comments,
please visit us on the Forums.

FAQ > Prelude's Corner > All about structures

This item was added on: 2004/01/21

All about Structures

Structures are a convenient way of grouping together related variables of possibly differing types. For example, if you wanted to create a single variable that represents a person, a structure is ideal. Such a variable would need several sub-variables, for example:


Person
  Name
  Age
  Height
  

Creating structures

Well, this is easily done with a simple structure declaration:


struct Person {
  char *name;
  int age;
  float height;
};


As you'll notice, a structure declaration begins with the keyword struct followed by the name of the structure. The name is optional. This sounds weird until you realize that a structure declaration defines a type. In other words, you can declare variables right after the closing brace:


struct {
  char *name;
  int age;
  float height;
} Prelude, Generic_person;


One thing to note however, is that if you omit the name (also known as the structure tag) from the declaration, you must declare variables of the structure in the same declaration. To declare variables of the structure later, you need the name of a type in one of two ways:

With a structure tag:


struct Person {
  char *name;
  int age;
  float height;
};


struct Person Prelude;

With a typedef:


typedef struct {
  char *name;
  int age;
  float height;
} Person_t;

Person_t Prelude;


Both of these examples require some explanation. The first declares a structure just as we've been talking about, but it doesn't include the declaration of a structure variable. Because the structure has a tag, we can declare a structure variable later, but the struct keyword is required to tell the compiler that Person is a structure. This restriction is removed by C++, but the C solution is to use a typedef, which takes us to the second example.

While it may look funny, using a typedef with a structure declaration is exactly the same as with any other type. It helps to remove the member variables and get a good look at the structure of the typedef:


typedef int INT;
typedef struct {} STRUCT;


Both are the same: the typedef keyword followed by the type to create an alias for, and finally the alias itself. But with larger structures this can become difficult to read, so the declaration is broken up into separate lines and indented to be readable as in our example above. Also notice that the struct keyword isn't needed when using the alias, this is because the keyword was included as a part of the typedef, adding another would be redundant and an error because Person_t isn't a struct, it's a typedef of a struct.

Like any variable, structure variables can be initialized with a list enclosed in braces:


struct Person Prelude = {"Prelude", 25, 5.9f};


Initialization is exactly the same when a variable is declared directly within the structure declaration:


struct {
  char *name;
  int age;
  float height;
} Prelude = {"Prelude", 25, 5.9f};


A structure variable can also be initialized by assigning an existing structure variable (in which a copy is made) or by calling a function that returns a structure of the same type.

Accessing structure variables

Now that we have a structure variable, it would be a good idea to try and get access to the member variables. If that weren't possible, structures wouldn't be very useful. To get to a particular member of a structure, the structure-name.member syntax is used, also called the dot operator:


printf ( "Name:   %s\n", Prelude.name );
printf ( "Age:    %d years\n", Prelude.age );
printf ( "Height: %.2f feet\n", Prelude.height );


Notice that the variable name, not the structure tag, goes on the left side of the dot operator and the member name goes on the right side. Structures can be nested inside of each other as well:


struct Person {
  char *name;
  int age;
  struct Height {
    int feet;
    int inches;
  } height;
};

or

struct Height {
  int feet;
  int inches;
} 

struct Person {
  char *name;
  int age;
  struct Height height;
};


When using the dot operator to access nested structures, just start on the left side with the outermost structure and go in:


printf ( "%d Inches\n", Prelude.height.inches );


When accessing a pointer to a structure, the pointer must first be dereferenced before the dot operator is used:


struct Person *Prelude;
...
printf ( "Name: %s\n", (*Prelude).name );


Now, using this (*structure-name).member syntax is awkward, ugly, and adds even more unnecessary parentheses to a language that already overuses them. So a special operator was added to the language because accessing the member of a pointer to a structure is so common and useful, structure-name->member, also called the arrow operator. Now we don't need to explicitly dereference the pointer or surround it with parentheses:


printf ( "Name: %s\n", Prelude->name );


Structures and Functions

Just like any other type, structures can be assigned to each other as well as passed to and returned from functions or have pointers to them passed to and returned from functions. There are three ways a structure can be passed to a function:

Member by member:


void print_person ( char *name, int age, float height )
{
  printf ( "Name:   %s\n", name );
  printf ( "Age:    %d years\n", age );
  printf ( "Height: %.2f feet\n", height );
}

print_person ( Prelude.name, Prelude.age, Prelude.height );


Structure by value:


void print_person ( struct Person pers )
{
  printf ( "Name:   %s\n", pers.name );
  printf ( "Age:    %d years\n", pers.age );
  printf ( "Height: %.2f feet\n", pers.height );
}

print_person ( Prelude );


Structure by simulated reference:


void print_person ( struct Person *pers )
{
  printf ( "Name:   %s\n", pers->name );
  printf ( "Age:    %d years\n", pers->age );
  printf ( "Height: %.2f feet\n", pers->height );
}

print_person ( &Prelude );


The preferred method in most cases is to pass a pointer to the structure. This saves space for larger structures and can often be faster. In fact, most data structure libraries will have a make_node type function that allocates memory to a pointer and returns it, or takes a pointer to a pointer and allocates memory to that:


struct Node *make_node ( void )
{
  struct Node new_node = malloc ( sizeof *new_node );

  /* Error check and set a default state */

  return new_node;
}

or

int make_node ( struct Node **new_node )
{
  int rc = EXIT_SUCCESS;

  new_node = malloc ( sizeof *new_node );
  /* Error check and set a default state */

  return rc;
}


Or something along those lines. :) This often simplifies the logic of such data structure algorithms as linked lists and binary trees, but becomes far more useful with sparse matrices and skip lists.

Arrays of Structures

Like any other type, you can make arrays of structures. The syntax is the same as with regular arrays:


struct Person {
  char *name;
  int age;
  float height;
} people[] = {
  {"Prelude", 25, 5.9f},
  {"Generic person", 20, 6.0f}
};

Or

struct Person {
  char *name;
  int age;
  float height;
};

struct Person people[] = {
  {"Prelude", 25, 5.9f},
  {"Generic person", 20, 6.0f}
};


To access an individual member, be sure to index the array before using the dot operator:


printf ( "Name: %s\n", people[0].name );


Miscellany

Just in case there is still some confusion, structures and structure variables can be declared locally if need be:


#include <stdio.h>

int main ( void )
{
  struct Person {
    char *name;
    int age;
    float height;
  };
  
  struct Person Prelude = {"Prelude", 25, 5.9f};
  
  printf ( "Name:   %s\n", Prelude.name );
  printf ( "Age:    %d years\n", Prelude.age );
  printf ( "Height: %.2f feet\n", Prelude.height );
  
  return 0;
}


Structures can contain pointers to a structure of the same type. Also called a self-referential structure:


struct Node {
  void *data;
  struct Node *next;
};


This is a typical node structure for a single item in a linked list. While this feature may seem confusing, keep in mind that when a Node variable is declared, only a pointer to a Node is created for the member, not an actual Node. If an actual Node were created, you would have a recursive declaration, and Nodes would be created until you run out of memory. But a pointer is safe because it doesn't behave recursively in this situation.

Two different structures can refer to each other. This is handled in much the same way as a structure referring to itself:


struct a {
  struct b *p;
};

struct b {
  struct a *p;
};


There is another feature of structures that can be useful sometimes called bit-fields. They're an advanced topic that I will cover in another tutorial, along with another type of structure called a union.

Script provided by SmartCGIs