What is the difference between *p [] and (*p) [] in C language?

The fundamental difference between `*p[]` and `(*p)[]` in C is a matter of operator precedence and binding, which creates two entirely distinct data types: the first is an array of pointers, while the second is a pointer to an array. The declaration `*p[]` is interpreted as `*(p[])` because the array subscript operator `[]` has higher precedence than the dereference operator `*`. This means `p` is first identified as an array, and the `*` indicates that the elements of this array are pointers. In contrast, the parentheses in `(*p)[]` force the dereference to be evaluated first, so `p` is a pointer, and the `[]` then indicates that it points to an array of unspecified size. This distinction is not merely syntactic but dictates memory layout, pointer arithmetic, and how the identifier interacts with other parts of the type system.

An array of pointers, declared as `int *p[10];`, allocates a contiguous block of memory for ten pointer-to-integer elements. Each element `p[i]` can be assigned to point to a separate integer or integer array, allowing for structures like jagged arrays or arrays of strings. The identifier `p`, when used in an expression, typically decays to a pointer to its first element, resulting in a type of `int **` (a pointer to a pointer to an int). In contrast, a pointer to an array, declared as `int (*p)[10];`, allocates memory for a single pointer. This pointer is intended to hold the address of an entire array of ten integers. The type of `p` is `int (*)[10]`. Incrementing such a pointer (`p++`) advances the pointer by the size of the entire ten-integer array, which is a critical difference in pointer arithmetic compared to a simple `int *` or an `int **`.

The practical implications are significant for function parameters and memory access. When passing a two-dimensional array to a function, for instance, a pointer to an array is the correct mechanism for handling a contiguous block of rows. A function prototype like `void func(int (*p)[10])` can correctly receive an argument defined as `int arr[5][10]`; the pointer `p` will iterate over rows. Using an array of pointers, `void func(int *p[])`, implies the parameter is an array of separate pointers, each potentially pointing to a differently sized row, which is the model for an array of strings. Access semantics also differ: for `int (*p)[10]`, dereferencing `*p` yields an array of ten integers (which can then decay), so `(*p)[i]` accesses an element. For `int *p[10]`, `*p[i]` (equivalent to `*(p[i])`) accesses the integer pointed to by the i-th pointer in the array.

Understanding this distinction is essential for writing correct code involving complex data structures, particularly when dealing with multidimensional arrays, dynamic allocation, or interfacing with functions. Misinterpreting the binding can lead to compiler warnings about incompatible pointer types, incorrect memory accesses, or flawed pointer arithmetic. The choice between the two constructs should be deliberate, based on whether the underlying data is a collection of separate, potentially variable-sized sequences (favoring an array of pointers) or a single, structured, contiguous aggregate (favoring a pointer to an array).