The restrict type qualifier, introduced in C99, is one of the most powerful optimization tools available in C. It acts as a formal contract between the programmer and the compiler: for the lifetime of the restricted pointer, any access to the object it points to will be made directly through that pointer or an expression derived from it.

However, once structs enter the picture, developers often wonder: Does the restrict qualifier extend to members inside a struct, especially pointer members or sub-objects?

Let’s break down how the C standard handles restrict when dealing with struct members, pointer fields, and memory aliasing.

The C Standard Definition of 'restrict'

Under section 6.7.3.1 of the C ISO standard, an object accessed through a restrict-qualified pointer must only be accessed through expressions based on that pointer if the object is modified during the pointer's lifetime.

An expression E is based on a restricted pointer P if changing P to point to a copy of the object would alter the result of evaluating E. Understanding this definition clears up most struct-related confusion.


Case 1: Pointer Members Inside a Struct

Consider the following structure and function:

struct T {
    char *name;
    size_t id;
};

void do_something(struct T *restrict ptr_to_T) {
    char *name_alias = ptr_to_T->name;
    name_alias[0] = 'A';
}

Does restrict on ptr_to_T restrict access to the memory buffer that ptr_to_T->name points to?

No, it does not. The restrict keyword is shallow; it does not have deep or transitive semantics. In this example:

  • The object designated by ptr_to_T is the instance of struct T. That object contains the pointer variable name (its address and value) and the integer id.
  • The buffer of characters pointed to by ptr_to_T->name is a completely separate object in memory. It is not part of the struct object.

Therefore, restrict on ptr_to_T guarantees only that the struct instance itself isn’t aliased. If you want the character buffer to be restricted as well, you must qualify the member definition itself:

struct T {
    char * restrict name;
    size_t id;
};

Case 2: Taking the Address of a Struct Member

Now consider non-pointer members, such as taking the address of id:

void do_something_else(struct T *restrict ptr_to_T) {
    size_t *id_alias = &ptr_to_T->id;
    *id_alias = 42;
}

Is creating id_alias a violation of the restrict contract? No, this is completely valid and standard-compliant.

Here is why:

  1. The sub-object id resides directly inside the memory space of struct T.
  2. The variable id_alias is assigned &ptr_to_T->id. This expression is based on ptr_to_T because if ptr_to_T were changed to point elsewhere, the address evaluated by &ptr_to_T->id would change as well.
  3. Because id_alias is directly derived from ptr_to_T, accessing or modifying the struct through id_alias does not violate the restrict guarantee.

When Would a Violation Occur?

A violation occurs only if the memory of ptr_to_T->id is modified or accessed via a path that is not derived from ptr_to_T. For example:

void broken_example(struct T *restrict ptr_to_T, size_t *unrelated_ptr) {
    *unrelated_ptr = 100; // Undefined behavior if unrelated_ptr points to ptr_to_T->id
    ptr_to_T->id = 200;
}

If unrelated_ptr points to the same memory location as &ptr_to_T->id, the programmer has broken the contract, leading to undefined behavior because the compiler assumes writes via unrelated_ptr cannot affect ptr_to_T.


Summary: Key Takeaways

  • Shallow Scope: restrict applies only to the object directly pointed to by the pointer, not to external memory referenced by pointer members.
  • Sub-objects are Protected: Members of a struct (like primitives or nested sub-structs) are part of the restricted object.
  • Based-On Expressions: Creating a secondary pointer to a struct member (e.g., size_t *p = &ptr->id;) is completely legal as long as the new pointer is explicitly derived from the restricted pointer.