How to understand **p and *p [ ] and (*p) [ ] in C language?
Understanding the distinctions between `*p`, `*p[]`, and `(*p)[]` in C requires a precise grasp of declarator syntax, which dictates whether an identifier is a pointer, an array, or a combination thereof, and crucially, the operator precedence between the indirection (`*`) and subscript (`[]`) operators. The core principle is that array subscripts bind more tightly than the dereference operator. Therefore, `*p` is simply a pointer to a type, while `*p[]` is an "array of pointers," and `(*p)[]` is a "pointer to an array." Misinterpreting these declarators leads to catastrophic type mismatches and runtime errors, as each defines a fundamentally different memory access pattern and arithmetic.
The declaration `int *p;` establishes `p` as a pointer to an integer. It holds the address of a single `int`, but through pointer arithmetic, it can be used to traverse a sequence of integers in memory. In contrast, `int *p[];` declares `p` as an array of pointers to integers. The `[]` has higher precedence, so the identifier `p` is first an array; the `*` then indicates that each element of this array is of type `int*`. This construct is central to data structures like command-line argument vectors, where `argv` is an array of pointers to character strings. The size of the array must typically be specified, as in `int *p[10];`, creating ten distinct integer pointers contiguous in memory.
The construct `int (*p)[];` uses parentheses to override precedence, making `p` a pointer to an array of integers of unspecified size. This is a less common but powerful declaration for handling multi-dimensional arrays idiomatically. Here, `p` does not point to a single integer but to an entire array block. Incrementing `p` by one (`p + 1`) advances the pointer by the size of the entire array it points to, which is essential for correctly traversing a two-dimensional array when rows are contiguous. For example, if `p` points to the first row of a 2D array, `p + 1` points to the second row. To access an element, one must first dereference the pointer to get the array, then apply a subscript: `(*p)[i]` accesses the `i`-th element of the pointed-to array.
The practical implications are significant for function interfaces and memory safety. A function parameter declared as `int *p[]` is adjusted by the compiler to `int **p`, a pointer to a pointer, suitable for receiving an array of pointers. A parameter declared as `int (*p)[]` remains a pointer to an array of incomplete type, often requiring an accompanying parameter for the array's dimension. Using the wrong declarator leads to incorrect pointer arithmetic; treating a pointer-to-array as a simple pointer will cause the program to calculate offsets incorrectly, accessing memory far beyond the intended element. Mastery of these forms is not academic but foundational for writing correct code involving complex data layouts, dynamic multi-dimensional arrays, and functions that operate on them.