Question

C Arrow Operator (->): Access Struct Members Through Pointers

cpointerssyntax

Question

While learning C pointers, I encountered the -> (arrow) operator without an explanation. Is it the equivalent of the . (dot) operator when working with a pointer to a structure? Please explain what it does and provide a code example.

Short Answer

The C arrow operator, ->, accesses a structure or union member through a pointer. It is shorthand for dereferencing the pointer and then using the dot operator: pointer->member is equivalent to (*pointer).member. You will learn when to use . versus ->, how the expression is evaluated, and how this appears in real C programs.

Concept

In C, a struct groups related values into one custom type. You use the dot operator (.) when you have the structure value itself:

struct Person person;
person.age = 30;

A pointer stores the address of another value. If you have a pointer to a structure, you must access the structure located at that address. The arrow operator (->) does this in one expression:

struct Person *person_ptr = &person;
person_ptr->age = 30;

This is exactly equivalent to:

(*person_ptr).age = 30;

The parentheses are essential because . has higher precedence than unary *. Without parentheses, *person_ptr.age would try to access age from person_ptr itself before dereferencing it, which is not what you want.

-> works with pointers to struct and union objects. It accesses data members such as age, name, or total.

Unlike Java or C#, ordinary C functions are not members of a structure. C can store a function pointer inside a structure, however, and -> can access that function-pointer field before it is called.

Mental Model

Think of a structure value as a filing cabinet you are standing next to. With the dot operator, you open a labeled drawer directly:

person.age

A pointer is a slip of paper containing the cabinet's address. With the arrow operator, you follow the address to the cabinet and open its labeled drawer:

person_ptr->age

So -> means: go to the structure this pointer points to, then select this member.

Take Quiz

Syntax and Examples

The core rule is:

pointer_to_struct->member

It means:

(*pointer_to_struct).member

Basic example

#include <stdio.h>

struct Book {
    const char *title;
    int pages;
};

int main(void) {
    struct Book book = {"The C Programming Language", 272};
    struct Book *book_ptr = &book;

    printf("%s has %d pages.\n", book.title, book.pages);
    printf("%s has %d pages.\n", book_ptr->title, book_ptr->pages);

    book_ptr->pages = 275;
    printf("Updated page count: %d\n", book.pages);

    return 0;
}

book is a structure value, so it uses .. book_ptr contains the address of book, so it uses ->.

When book_ptr->pages = 275; runs, it changes the original book.pages field because the pointer points to that object.

Accessing a nested structure

Use -> to reach a member through a pointer. If that member is itself a structure value, use . for its member:

struct Date {
    int year;
};

struct Event {
    struct Date date;
};

struct Event event = {{2025}};
struct Event *event_ptr = &event;

int year = event_ptr->date.year;

Here, event_ptr is a pointer, so event_ptr->date is used first. date is a structure value, so .year follows it.

Step by Step Execution

Consider this program:

#include <stdio.h>

struct Counter {
    int value;
};

int main(void) {
    struct Counter counter = {10};
    struct Counter *ptr = &counter;

    ptr->value += 5;

    printf("%d\n", counter.value);
    return 0;
}

Execution trace:

  1. counter is created with one member: counter.value is 10.
  2. &counter produces the memory address of counter.
  3. ptr stores that address, so ptr points to counter.
  4. ptr->value means (*ptr).value: dereference ptr, producing counter, then select value.
  5. ptr->value += 5 changes the value from 10 to 15.
  6. counter.value is now also 15, because there is only one Counter object; ptr refers to it.
  7. The program prints 15.

Real World Use Cases

The arrow operator is common whenever a C program passes structure pointers rather than copying whole structures.

  • Function parameters: A function receives a pointer so it can update the caller's structure.

    void set_score(struct Player *player, int score) {
        player->score = score;
    }
    
  • Dynamically allocated objects: malloc returns a pointer, so allocated structures use ->.

    struct Node *node = malloc(sizeof *node);
    node->value = 42;
    
  • Linked lists: Each node often stores a pointer to the next node.

    current = current->next;
    
  • Library APIs: Many C libraries use opaque structure pointers, such as file handles. For example, FILE * is used with library functions, although its internal members should not be accessed directly.

  • Configuration and state objects: Programs often pass a pointer to a shared application state or configuration structure to avoid copying it.

Take Quiz

Real Codebase Usage

In production C code, -> is most often seen in functions that receive pointers to structures.

Update an object owned by the caller

struct Account {
    int balance;
};

void deposit(struct Account *account, int amount) {
    if (account == NULL || amount <= 0) {
        return;
    }

    account->balance += amount;
}

This uses a guard clause to reject an invalid pointer or amount before accessing account->balance.

Read-only access with const

If a function should inspect a structure without changing it, use a pointer-to-const:

#include <stdio.h>

struct User {
    const char *name;
    int id;
};

void print_user(const struct User *user) {
    if (user == NULL) {
        return;
    }

    printf("%s (#%d)\n", user->name, user->id);
}

user->id can be read, but assigning to it inside print_user is not allowed because user points to const data.

Function pointers stored in structures

C does not have instance methods, but a structure can contain a function pointer:

#include <stdio.h>

struct Button {
    void (*on_click)(void);
};

void save_file(void) {
    puts("Saving file");
}

int main(void) {
    struct Button button = {save_file};
    struct Button *button_ptr = &button;

    button_ptr->on_click();
    return 0;
}

The arrow accesses the on_click field. That field is a function pointer, which is then called.

Common Mistakes

Using . on a pointer

This is incorrect because person_ptr is a pointer, not a struct Person value:

struct Person *person_ptr;
person_ptr.age = 30; /* Incorrect */

Use:

person_ptr->age = 30;

Forgetting parentheses with *

This is incorrect:

(*person_ptr.age) = 30; /* Incorrect */

It is parsed as *(person_ptr.age). Use either form below:

person_ptr->age = 30;
(*person_ptr).age = 30;

Dereferencing a NULL pointer

This has undefined behavior and often crashes:

struct Person *person_ptr = NULL;
person_ptr->age = 30; /* Undefined behavior */

Check pointers when they may be absent:

if (person_ptr != NULL) {
    person_ptr->age = 30;
}

Using an uninitialized pointer

A pointer must point to a valid object before -> is used:

struct Person *person_ptr;
person_ptr->age = 30; /* Undefined behavior */

Initialize it with the address of an existing object or dynamically allocate an object.

Using -> for an ordinary function

C functions do not belong to structures in the way class methods do in Java or C#. This is not valid unless run is a function-pointer member:

/* device->run(); only works if struct Device has a run function-pointer field */

An ordinary C function is called by name:

run_device(device_ptr);

Comparisons

SituationCorrect syntaxReason
You have a structure valueperson.ageperson is the structure itself.
You have a pointer to a structureperson_ptr->ageperson_ptr holds the structure's address.
Expanded arrow form(*person_ptr).ageDereference first, then access the member.
You have a pointer to a primitive value*number_ptrPrimitive values have no named members.
You have a pointer to a pointer(*person_ptr_ptr)->ageDereference once to obtain a structure pointer, then use ->.

-> is not a general "pointer dot" operator for every type. It only makes sense when the dereferenced value is a structure or union with named members.

Take Quiz

Cheat Sheet

  • Use . with a structure or union value: item.count.

  • Use -> with a pointer to a structure or union: item_ptr->count.

  • Equivalent expressions:

    item_ptr->count
    (*item_ptr).count
    
  • Parentheses are required in the expanded form: (*ptr).member.

  • ptr->member requires ptr to point to a valid structure or union object.

  • Do not use -> through NULL, a dangling pointer, or an uninitialized pointer.

  • C has no normal object methods. ptr->callback() is valid only when callback is a function-pointer member.

  • A nested value often combines operators: ptr->address.zip_code.

FAQ

What does -> mean in C?

It dereferences a pointer to a structure or union and accesses one of its members. ptr->member is shorthand for (*ptr).member.

When should I use . instead of -> in C?

Use . when your variable is the actual structure value. Use -> when your variable is a pointer to that structure.

Is ptr->member exactly the same as (*ptr).member?

Yes. They access the same member in the same object. The arrow form is shorter and easier to read.

Why are parentheses needed in (*ptr).member?

Because the dot operator binds more tightly than *. Parentheses ensure the pointer is dereferenced before selecting the member.

Can I use the arrow operator with arrays?

Not directly for an array itself. You can use it when a structure pointer has an array member, such as record_ptr->scores[0].

Can I call functions with -> in C?

Only if the selected structure member is a function pointer. Ordinary C functions are not structure members.

Does -> allocate memory or copy a structure?

No. It only accesses a member of the structure at the address stored in the pointer.

Take Quiz

Mini Project

Description

Build a small linked-list traversal program. Each task is stored in a structure, and each structure points to the next task. This demonstrates a practical reason that C code frequently uses the arrow operator: navigating dynamically connected objects through pointers.

Goal

Print every task in a linked list and count the completed tasks using -> to access node members.

Requirements

Create a struct Task with a title, completion flag, and pointer to the next task. Create three task variables and link them in order. Write a function that receives a pointer to the first task. Print each task title and its completion status. Return the number of completed tasks.

Take Quiz

Keep learning

Related questions

View more