Fundamental C - Basic Structs
Written by Harry Fairhead   
Monday, 29 April 2019
Article Index
Fundamental C - Basic Structs
Value Semantics
Pointers to Structs

Pointers to Structs

Using value semantics for structs makes reasonable sense if the struct is small and uses only a small amount of memory.

The reason arrays use reference semantics and structs use value semantics is that generally arrays are large whereas structs are small. Of course, there are times when structs are not small – mostly when one or more of the fields is an array.

Once structs get bigger then you should pass them by reference.

How big is it reasonable to pass a struct by value? The answer depends on the machine architecture you are working with but the important point is that the struct’s values are usually passed on the stack so it is the stack size that matters, not the overall amount of memory available. This is particularly relevant if you pass a large struct to a function which then passes it to another function and so on. Each function call allocates the struct on the stack and so uses up increasing amounts of memory – this is particularly important in recursive calls.

In practice, things are more complex because the compiler could put the struct into a register and avoid the use of the stack, but this depends on the struct being very small and there being a register available for use.

In nearly all cases, it is faster to pass a struct by reference and this is generally considered to be best practice unless there is a really good reason not to.

How do you pass or return a struct by reference? Easy, you simply use a pointer to the struct. For example, the previous addToAge function that didn’t work because person was passed by value, can be made to work by passing and using a pointer to the struct:

void addToAge(struct myStruct *person) {
    (*person).age = (*person).age + 1;

Notice that the parentheses are needed because the dereference operator has a lower precedence than the dot operator. Without the parentheses the expression would be equivalent to *(person.age), which wouldn’t evaluate correctly.

Now you have to call addToAge using a pointer:


and in this case the age field of me is changed by the function, which is what you would expect.

You can use a pointer to struct to return a struct but as in the case of an array the struct so returned cannot be a local variable. In other words you have to use malloc to allocate the memory on the heap.

For example, the makePerson function can be rewritten to create a struct on the heap:

struct myStruct* makePerson(char *name,int age){
    struct myStruct *person=
(struct myStruct*)malloc(sizeof(struct myStruct)); strncpy((*person).name,name,25); (*person).age=age; return person; }

The cast isn’t actually necessary but it makes clear what you intend. Notice that the function now returns a pointer to the struct and has to be called using:

struct myStruct *me = makePerson("harry",19);


The only downside of using a pointer to struct is the slightly cumbersome way you have to dereference the pointer within parentheses. To make this slightly easier, C introduced the -> notation which dereferences the pointer and accesses the field in one go, i.e. it’s the equivalent of * and dot.

For example the makePerson function could be written:

struct myStruct* makePerson(char *name,int age){
    struct myStruct *person=
(struct myStruct*)malloc(sizeof(struct myStruct)); strncpy(person->name,name,25); person->age=age; return person; }

and to print the fields you can use:

printf("%d %s\n", me->age, me->name);

It is generally considered good practice to use the arrow operator -> when you have a pointer to struct.

Notice that you can’t simply use pointer arithmetic to access the fields of a struct. The reason is that adding one to a struct pointer treats it as if it was an array of structs and so it adds sizeof(struct) to the pointer. If you really want to access the fields you have to cast the pointer to the type of the first field and add one, then the next field and add one and so on until you reach the field of your choice. Even this isn’t likely to work because of padding – see later.

In short don’t try to access the fields of structs using pointer arithmetic, use the arrow -> notation.

Sections in chapter but not in this extract:

  • Using typedef
  • Nested Structs
  • Padding
  • Union
  • Bit Fields


  • A struct is a block of memory used to store a set sequence of data types - its fields. It corresponds to the idea of a record.

  • A struct is declared as a type which can be used to create as many instances of the type as required.

  • The fields of a struct are accessed using the dot notation, struct.field, or if you are using a pointer to a struct then you can use the arrow notation, pstruct->field.

  • Structs are assigned using value semantics. That is a copy is created on assignment, or when used as a parameter or returned via a function. The copy is shallow – i.e. any pointers are copied but not memory external to the struct they may point at.

  • A typedef statement can be used to create an alias for a type. It is often used in conjunction with a struct declaration and this can be confusing for the beginner.

  • Structs can be nested. The nested struct has memory allocated for it within the containing struct.

  • A complication of using structs is padding. Extra bytes often have to be added to a struct to make its fields align with word boundaries.

  • A union is a special type of struct that only allocates memory for the largest of its fields. All other fields share the same memory. A union can be used to alias types, that is the same block of memory is treated as different types. This is the only standard based way of achieving type punning.

  • A struct field can be allocated at the bit level. This sounds attractive but implementation differences make it worth avoiding.



Fundamental C: Getting Closer To The Machine

Now available as a paperback and ebook from Amazon.

  1. About C
      Extract Dependent v Independent
                  & Undefined Behavio
  2. Getting Started With C Using NetBeans
  3. Control Structures and Data
  4. Variables
      Extract Variables
  5. Arithmetic  and Representation
      Extract Arithmetic and Representation
  6. Operators and Expression
      Extract: Expressions
      Extract Side Effects, Sequence Points And Lazy Evaluation
      First Draft of Chapter: Low Down Data
  7. Functions Scope and Lifetime
  8. Arrays
      Extract  Simple Arrays
      Extract  Ennumerations
  9. Strings
      Extract  Simple Strings
    Extract: String I/O ***NEW!!
  10. Pointers
      Extract  Starting Pointers
      Extract  Pointers, Cast & Type Punning
  11. Structs
      Extract Basic Structs
      Extract Typedef
  12. Bit Manipulation
      Extract Basic Bits
      Extract Shifts And Rotates 
  13. Files
     Extract Files
    Extract Random Access Files 
  14. Compiling C – Preprocessor, Compiler, Linker
     Extract Compilation & Preprocessor

Also see the companion volume: Applying C






Harry Fairhead is the author of Raspberry Pi IoT in C (I/O Press). This extract is from his new book, Fundamental C, where he takes an in-depth look at C for use in any close-to-the-hardware context.

Related Articles

Remote C/C++ Development With NetBeans

Raspberry Pi And The IoT In C

Getting Started With C/C++ On The Micro:bit

To be informed about new articles on I Programmer, sign up for our weekly newsletter, subscribe to the RSS feed and follow us on Twitter, Facebook or Linkedin.


Pg_lakehouse Makes PostgreSQL Quack

Pg_Lakehouse from ParadeDB is an extension that turns PostgreSQL into the analytical engine of DuckDB. Why is that useful? How do you use it?

Look Once to Hear - A Spy's Dream Come True

Deep learning has triumphed again. You can don a pair of headphones, look at a person talking and from then on the system will track the person so you can hear them as they move away or become swamped [ ... ]

More News

kotlin book



or email your comment to:




Last Updated ( Monday, 27 May 2019 )