Basic Rules

Learn more about the type system used in C. Discover a simple process for understanding complex declarations.

Introduction

We’ve already seen our fair share of declarations involving pointers, such as pointers to variables, pointers to arrays, and pointers to functions. Examples of such declarations are as follows:

int* ptr; //ptr is a pointer to int
int x[10]; //x is an array of 10 elements
void(*funcPtr)(int x); //func is a pointer to a function taking an int as argument and returning void

These alone are pretty straightforward to read, but when we start to combine them, reading them can become rather complex. We didn’t define any specific rules for reading them, but we can do it intuitively by looking at the definition.

On the other hand, we also saw declarations like the following:

char* x[10]; //x is an array of 10 pointers to char
void(*funcPtr[])(int); //funcPtr is an array of pointers to functions taking an int as argument and returning void

Huh, these are pretty hard to read without clear rules and a step-by-step process.

Derived types

Recall that in one of the first lessons, we said to view pointers as another data type, similar to int, float, and other usual data types. It was only partially correct. Pointers, together with array and function types, are called derived data types. They enrich the basic data types such as int, float, and char.

We’ll have a mix of standard and derived data types. We already know how to read them, but let’s go over them one more time:

  • * is a pointer type. Read it as “pointer to.”
  • [] is an array type.
    • We can encounter both the sized ([SIZE]) and unsized form ([]).
    • If the array has size, read it as “array of size of type.”
    • If the array doesn’t have size, read it as “array of type.”
    • It’s up to us if we choose to ignore the size and always treat arrays as unsized. However, we can do it only for the first dimension of the array (the one that decays into a pointer). Recall that arr[5][10] loses the information about the first dimension only. It’s equivalent to arr[][10]. Don’t ignore the second and third (and so on) dimension sizes.
  • () or (args) is a function definition. Read it as “function returning type and taking arguments of types.”

Operator precedence

Since an expression can combine all three derived types, we need to define an order for reading them. The general rule will take care of it, but for now, let’s also define their precedence.

Note: () and [] have higher precedence than *.

The operator precedence explains why, in the past, we used () to change the priority. For example, if we want to have a pointer to an array of 10 integers, we have to write the following:

int(*ptr)[10]; //ptr is a pointer to an array of size 10 of integers

If we remove (), we get int *ptr[10]. Since [] has a higher priority than *, we’ll read it first, and then *. We get that ptr is an array of size 10 of int pointers. However, we wanted a pointer to an array of 10 integers. To obtain the proper type, we use () to increase the priority of * and read it first, before [].

Right-left rule

We only need to know the general rule for reading complex declarations.

  1. Start from the identifier (the name).
  2. Go right as much as possible without going over a closing brace.
  3. Go left as much as possible from the identifier without going over an opening brace.
  4. Exit the inner braces and repeat.

Let’s take our declaration for an array of function pointers to understand the process.

void(*funcPtr[])(int);

See the animation below.

Get hands-on with 1200+ tech skills courses.