Structures in C

These notes cover the use of statically allocated structures in C. If you are interested in learning how to use dynamically allocated structures, take a look at my Fall 2011 ECE 209 notes.

On the other hand, if you really want to use dynamically allocated structures, you might be better off using C++. You might lose a little on performance, but you’d gain a lot in reliability with the type-safe operators of C++. Finally, I will mention that dynamic allocation is generally avoided in embedded systems programming because of its unpredictable use of memory.

Heterogeneous data

char *Name[50] ;
char  Abbrev[50][2] ;
int   Population[50] ;

Name[12]       = "North Carolina" ;
Abbrev[12][0]  = 'N' ;
Abbrev[12][1]  = 'C' ;
Population[12] = 9535483 ;

Is placing these values in three different arrays convenient, logical, or efficient?

Declaring and using structures

The C struct constructor allows the definition of a heterogeneous data structures with fields or members of different types. The name of each field within a structure definition must be unique.

struct state {
  char *Name ;
  char Abbrev[2] ;
  int Population ;
} ;

struct state NC ;

NC.Name       = "North Carolina" ;
NC.Abbrev[0]  = 'N' ;
NC.Abbrev[1]  = 'C' ;
NC.Population = 9535483 ;

The dot operator

The dot operator joins a structure expression with a field or member name.

The . operator has the highest precedence level and has left-to-right precedence. Other operators at this level are:

A horrendous example of this would be f("United States")[30].Population++ used as a expression; however, you do often find long sequences such as USA[12].Name[0].

Structures as variables

In ANSI C, one structure can be assigned (with =) to another and structures can be passed to and returned from functions. This involves copying the entire structure and is reasonable only with very small structures.

In C you can't return an array from a function, but you can return a structure containing an array. Now that is odd.

Initialization of structures

It is possible to initialize an entire structure with a single statement. Be careful to get all the fields in order.

struct state NC = { "North Carolina", {'N','C'}, 9535483 } ;

Types of structures

Effectively every struct definition introduces a new programmer-defined type. The typedef statement allows you to give a more convenient name for the struct.

typedef struct state State ;

State NC ;

The FILE “type” used for file I/O operations in C is typically defined with a typedef of a C structure. The quotes are used because technically a typedef is not a new type, but rather a “synonym” for an existing type.

By the way, the C structure is rather like a method-less class in either C++ or Java. Also, in C++ you can avoid the typedef,

Arrays of structures

There is nothing unusual about arrays of structures.

struct state USA[50] ;

USA[12].Name       = "North Carolina" ;
USA[12].Abbrev[0]  = 'N' ;
USA[12].Abbrev[1]  = 'C' ;
USA[12].Population = 9535483 ;

Nested structures

Structures nested a couple of levels can be useful and are quite common.

struct county {
  char *Name ;
  char  Population ;
  struct state State ;
} ;

struct county Haywood ;

Haywood.Name = "Haywood" ;
Haywood.State.Name = "North Carolina" ;

Structures, procedures, and pointers

Suppose you needed to write a function that increased the population of a state. Here is a first pass at that function.

void addPeople(State S, double growth) {
  S.Population = (int)((1 + growth) * S.Population) ;

This won’t work because addPeople will be modifying a copy of the state data structure and the modified copy will be lost on the stack when addPeople returns. There’s an additional problem: If the state data structure is very large, there will be a non-trivial cost to copying the structure. This action might even overflow the stack.

A better implementation would be to pass a pointer to the state structure with a call like the following:

  addPeople(&NC, 0.134) ;

Because pointers to structures are frequency used in C, there is a special operator -> (looks like a pointing sign) in C. In C, X->f is treated as syntactic sugar for (*X).f . With pointers and ->, the addPeople function can be written as this:

void addPeople(State *S, double growth) {
  S->Population = (int)((1 + growth) * S->Population) ;