Understanding C's restrict Keyword: Does It Apply to Struct Members?
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_Tis the instance ofstruct T. That object contains the pointer variablename(its address and value) and the integerid. - The buffer of characters pointed to by
ptr_to_T->nameis 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:
- The sub-object
idresides directly inside the memory space ofstruct T. - The variable
id_aliasis assigned&ptr_to_T->id. This expression is based onptr_to_Tbecause ifptr_to_Twere changed to point elsewhere, the address evaluated by&ptr_to_T->idwould change as well. - Because
id_aliasis directly derived fromptr_to_T, accessing or modifying the struct throughid_aliasdoes 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:
restrictapplies 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.