あなたの投稿で言っていることは完全に正しいです。すべてのC開発者は、C言語についてある程度の熟練度に達した場合(ある場合)、まったく同じ発見とまったく同じ結論に到達すると思います。
アプリケーション領域の詳細が特定の固定サイズの配列を要求する場合(配列サイズはコンパイル時の定数です)、そのような配列を関数に渡す唯一の適切な方法は、ポインター配列パラメーターを使用することです。
void foo(char (*p)[10]);
(C ++言語では、これも参照で行われます
void foo(char (&p)[10]);
)。
これにより、言語レベルの型チェックが有効になり、正確なサイズの配列が引数として指定されていることが確認されます。実際、多くの場合、人々はこの手法を実現せずに暗黙的に使用し、typedef名の後ろに配列型を隠しています。
typedef int Vector3d[3];
void transform(Vector3d *vector);
/* equivalent to `void transform(int (*vector)[3])` */
...
Vector3d vec;
...
transform(&vec);
さらに、上記のコードは、Vector3d
型が配列またはであることに関連して不変であることに注意してくださいstruct
。の定義はVector3d
いつでも配列からaに切り替えることができstruct
、関数の宣言を変更する必要はありません。どちらの場合でも、関数は「参照によって」集約オブジェクトを受け取ります(これには例外がありますが、この説明の文脈ではこれは当てはまります)。
ただし、あまりにも多くの人々が複雑な構文に混乱し、C言語のそのような機能を適切に使用するのに十分慣れていないために、この配列渡しのメソッドが頻繁に明示的に使用されることはありません。このため、平均的な実生活では、配列をその最初の要素へのポインタとして渡すのがより一般的な方法です。それは単に「よりシンプル」に見えます。
しかし実際には、配列を渡すために最初の要素へのポインターを使用することは非常にニッチなテクニックであり、トリックは非常に特定の目的を果たします:その唯一の目的は、異なるサイズ(つまり、ランタイムサイズ)の配列を渡すことを容易にすることです。ランタイムサイズの配列を本当に処理できるようにする必要がある場合、そのような配列を渡す適切な方法は、追加パラメーターによって提供される具体的なサイズを持つ最初の要素へのポインターによるものです。
void foo(char p[], unsigned plen);
実際、多くの場合、ランタイムサイズの配列を処理できると非常に便利です。これもメソッドの人気の原因となっています。多くのC開発者は、固定サイズの配列を処理する必要に遭遇しない(または認識しない)ため、適切な固定サイズの手法に気付かれないままです。
それにもかかわらず、配列サイズが固定されている場合は、要素へのポインタとして渡します
void foo(char p[])
主要な技術レベルのエラーであり、残念ながら最近かなり広まっています。そのような場合は、配列へのポインター手法がはるかに優れています。
固定サイズの配列受け渡し手法の採用を妨げる可能性のあるもう1つの理由は、動的に割り当てられた配列の型付けに対する単純なアプローチの優位性です。たとえば、プログラムがchar[10]
(例のように)タイプの固定配列を呼び出す場合、平均的な開発者は次のmalloc
ような配列を使用します。
char *p = malloc(10 * sizeof *p);
この配列を次のように宣言された関数に渡すことはできません
void foo(char (*p)[10]);
これは、平均的な開発者を混乱させ、さらに考えることなく固定サイズのパラメーター宣言を放棄させます。しかし実際には、問題の根本は素朴なmalloc
アプローチにあります。上記のmalloc
フォーマットは、ランタイムサイズの配列用に予約する必要があります。配列型がコンパイル時のサイズを持っている場合、malloc
それを行うより良い方法は次のようになります
char (*p)[10] = malloc(sizeof *p);
もちろん、これは上記の宣言に簡単に渡すことができます foo
foo(p);
コンパイラは適切な型チェックを実行します。しかし、これもまた、準備の整っていないC開発者にとっては非常に混乱します。そのため、「典型的な」平均的な日常のコードではあまり見られません。
10
、スコープ内の任意の変数で置き換えることができます