Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 29

Decoding Complex C Declarations: The Right-Left Rule

Recommended resources

Hello! Welcome to our next lesson.

In our last session, we differentiated between an array of pointers (int *p[10]) and a pointer to an array (int (*p)[10]). The key takeaway was that C's operator precedence rules ([] has higher precedence than *) dictate the interpretation, and parentheses are used to override this default behavior. We briefly mentioned a systematic method, the "right-left rule," for parsing these.

Today, we're going to master that method. The C syntax allows for declarations of remarkable complexity, especially when pointers, arrays, and functions are combined. Understanding these is not just an academic exercise; it's a critical skill for navigating complex codebases like OS kernels, drivers, and the AUTOSAR middleware you work with.

Our goal for this lesson is to parse complex C declarations involving pointers, arrays, and functions using the 'right-left rule'. By the end, you'll be able to decipher even the most intimidating C declarations.

The "Right-Left Rule": A Human C Compiler

The "right-left rule" (also sometimes called the "spiral rule") is a simple, deterministic algorithm for translating C declarations into plain English. It works by spiraling outwards from the variable name, processing operators in the same order of precedence that the compiler does.

Here are the fundamental steps:

  1. Start at the identifier (the variable name).
  2. Move right when you can. If you encounter:
    • [] or [N], say "array of..." or "array of size N of...".
    • () (with or without arguments), say "function returning...".
  3. Move left when you must. You must move left when you hit:
    • A closing parenthesis ).
    • The end of the declaration.
    • An operator with lower precedence than what's on the right.
      When moving left, if you encounter *, say "pointer to...".
  4. If you encounter an opening parenthesis (, you have finished a group. Jump out of the parentheses and continue the rule from there (usually by moving right again).
  5. Continue this process until you have consumed all the tokens. Finally, state the base type at the far left (e.g., int, const char, etc.).

To see this rule in action, the following video provides an excellent, step-by-step walkthrough of the logic and several examples.

Reading and Writing C Type Declarations Part 1/2

The video 'Reading and Writing C Type Declarations Part 1/2' from HackerCS clearly explains the components of a declaration and then introduces the 'right-left rule' with simple, memorable steps.

Watch from 00:12 to 04:48. Focus on the three core principles: 1) Start with the variable name. 2) Go right when you can, go left when you must. 3) End with the basic type. Pay close attention to the three conditions that force you to go left.


Applying the Rule: Functions and Pointers

Let's use the rule to decipher two classic, and often confused, declarations involving functions.

1. A function that returns a pointer:
char *func();

  • Step 1: Start at the identifier: func is...
  • Step 2: Move right. We see (). func is a function returning...
  • Step 3: Reached the end, so move left. We see *. func is a function returning a pointer to...
  • Step 4: Move left again. We see the base type char. func is a function returning a pointer to a char.

2. A pointer to a function:
char (*func)();

  • Step 1: Start at the identifier: func is...
  • Step 2: Move right, but hit a closing parenthesis ). So we must move left. We see *. func is a pointer to...
  • Step 3: We've hit the opening parenthesis (, so we jump out and continue right. We see (). func is a pointer to a function returning...
  • Step 4: Reached the end, so move left of the whole (*func)() group. We see the base type char. func is a pointer to a function returning a char.

The parentheses completely change the meaning by altering the order of operations, just as we saw with arrays in the last lesson.

The following resource provides more excellent, text-based walkthroughs. It also contains a valuable list of legal and illegal declarations.

C Right-Left Rule

Rick Ord's 'C Right-Left Rule' is a classic and concise reference for this technique. It reinforces the steps and provides clear examples.

Read the sections 'STEP 1', 'STEP 2', and 'STEP 3' to get another perspective on the rule. Then, review the two examples provided: int *p[]; and int *(*func())();. Follow the numbered steps to see how the author applies the rule.

Test your understanding!

Using the right-left rule, decipher the following two declarations. How are they different?

  1. long *p_arr[10];
  2. long (*func_ptr)(int);
Show answer
  1. long *p_arr[10];

    • p_arr is an array of 10 (right) pointers to (left) long.
    • This declares an array. sizeof(p_arr) would be 10 * sizeof(long *).
  2. long (*func_ptr)(int);

    • func_ptr is a pointer to (left, due to )) a function that takes an int and returns (right) a long.
    • This declares a single pointer variable. sizeof(func_ptr) would be sizeof(long (*)(int)), which is the size of a single pointer.

Conquering Complexity

Now, let's apply the rule to a more complex declaration that combines arrays, pointers, and functions. Declarations like this, while not something you should write, can appear in complex function pointer tables or macros in low-level code.

Let's watch the video from earlier demonstrate a very complex example. This will show how the same simple rules scale to any level of complexity.

Reading and Writing C Type Declarations Part 1/2

Let's return to the 'Reading and Writing C Type Declarations' video to see the rule applied to a truly convoluted declaration.

Watch the segment from 06:43 to 08:56. Follow along as the presenter parses char (*(*fu[ ])[8])();. Don't worry about memorizing it; focus on how consistently the 'right-when-you-can, left-when-you-must' rule is applied.

Here is a table with more examples of complex declarations and their interpretations, which can be a useful reference.

Complex C Declarations and Their Interpretations
This table shows five examples of complex C declarations and their plain English interpretations, illustrating combinations of pointers, arrays, and functions.

Illegal Declarations

It's important to note that while the right-left rule can help you read any declaration, it doesn't prevent you from writing an illegal one. C has a few fundamental restrictions:

  • An array of functions is illegal: int af[]();. You can, however, have an array of pointers to functions: int (*apf[])();.
  • A function cannot return a function: int ff()();. You can return a pointer to a function: int (*fpf())();.
  • A function cannot return an array: int fa()[];. You can return a pointer to an array: int (*fpa())[];.

Your experience with functional safety standards like ISO 26262 and MISRA-C highlights the importance of such language rules. Understanding what is not allowed is as important as understanding what is. The webpage from LINK that you read earlier contains a comprehensive list of legal vs. illegal examples at the end, which is a great reference.

Final Challenge!

Let's decipher one final, beastly declaration from the CodeProject article (LINK). Take your time and apply the rule step-by-step.

int * (* (*fp1) (int) ) [10];

What is fp1?

Show answer

Let's break it down:

  1. Start with the identifier: fp1 is...
  2. Go left inside the first parenthesis: ...a pointer to...
  3. Jump out of (*fp1) and go right. You see (int): ...a function that takes an int...
  4. The function must return something. Go left from the function group (*fp1)(int): ...and returns a pointer to...
  5. Jump out of the next set of parentheses and go right. You see [10]: ...an array of 10...
  6. Go left from the array. You see *: ...pointers to...
  7. Finally, go left to the base type: ...ints.

Putting it all together: fp1 is a pointer to a function that takes an int and returns a pointer to an array of 10 pointers to ints.


Conclusion

Congratulations on making it through some of the most complex syntax in the C language! While you may rarely write declarations this convoluted, the ability to read and understand them is a mark of an expert C programmer.

Key Takeaways:

  • The right-left rule is a reliable algorithm for parsing any C declaration by spiraling outwards from the identifier.
  • [] (array of) and () (function returning) have higher precedence than * (pointer to).
  • Parentheses () are used to override precedence, which is the key to distinguishing between concepts like "a pointer to a function" vs. "a function returning a pointer".
  • C has restrictions: you cannot have arrays of functions or functions that return arrays/functions directly. You must use a layer of pointer indirection.

Preview of the Next Lesson:
Now that we can confidently determine the precise type of complex pointer constructs, we are ready to use them. In the next lesson, we will apply pointer arithmetic to traverse multi-dimensional arrays. We'll see how the types we've been parsing, like int (*)[5], are exactly what the compiler needs to perform correct address calculations in 2D and 3D arrays.