関数の仮引数の宣言は、宣言文の形をしていますが、通常の変数の宣言とは違います。たとえば、次の2つの仮引数の宣言は、どちらも同じ意味になります。これは、(仮引数ではない)宣言文において*と[ ]とが別の意味を持っていたのとは異なるため、注意を要します。

void func(char *p)    ← char型へのポインタpを仮引数とするvoid型関数を宣言
void func(char p[])   ← char型を要素とする配列pを仮引数とするvoid型関数を宣言

C言語で関数を呼び出す際に、関数の引数は「値渡し」という方法で渡されます。これは、関数呼び出しもとの変数等(実引数)の値が、仮引数用のメモリ領域(通常はスタック)にコピーされた上で関数が呼び出されるということです。

引数がintやcharなどの数値であるか、またはポインタである場合は、実引数の値が仮引数の変数に代入されて関数に渡されるだけなので問題ありません。引数が配列の場合は(配列名を実引数にしますが)、決して配列全体のコピーが取られるわけではなく、配列名、すなわち配列の先頭アドレスを示すアドレス定数のみが仮引数の変数に代入されて関数に渡さます。アドレス定数を変数に代入して渡すということは、結局ポインタを渡しているのと同じです。

したがって、関数が呼び出された段階では、実引数がもともと配列名であったのかポインタであったのか区別がなくなります。また、仮引数は、関数内部のローカル変数として値の変更が可能なため、もともとアドレス定数だった値を、関数内部でインクリメント(+1)したり、別の値を代入したりすることすら可能になります。

以上のことから、仮引数の宣言では「char *p」と書いても「char p[]」と書いても同じであり、実際には(配列ではなく)ポインタが渡されているということが言えます。