4

最初は、関数を.hファイルに書き込んでから、それを.hに含めていました#include "myheader.h"。次に、これらのファイルに関数プロトタイプのみを追加し、実際のコードを別の.cファイルに入れる方がよいと誰かが私に言いました。これで、より多くの.cファイルをコンパイルして実行可能ファイルのみを生成できるようになりましたが、この時点では、コードが別のファイルにある場合にヘッダーファイルを追加する必要がある理由がわかりません。

さらに、システム内の標準Cライブラリ(のようなstdlib.h)を調べたところ、構造体の定義や定数などのみを格納しているように見えました...私はC(そして正直なところ、stdlib.h)があまり得意ではありません。私にとってはほとんど中国語でしたが、もちろん中国語には違和感はありませんでした:))、しかし私は「有効な」コードの1行を見つけませんでした。ただし、私は常に何も追加せずにそれを含め、「コード」が実際にそこにあるかのようにファイルをコンパイルします。

誰かがこれらのものがどのように機能するかを私に説明できますか?または、少なくとも、良いガイドを教えてください。グーグルとSOも検索しましたが、それを明確に説明するものは見つかりませんでした。

4

5 に答える 5

16

Cコードをコンパイルする場合、コンパイラーは、定義された名前、パラメーター・リスト、戻り値の型、およびオプションの修飾子を持つ特定の関数が存在することを実際に認識している必要があります。これらはすべて関数シグネチャと呼ばれ、特定の関数の存在がヘッダーファイルで宣言されます。この情報を取得すると、コンパイラはこの関数の呼び出しを検出すると、検索するパラメータのタイプを認識し、適切なタイプであるかどうかを制御して、コードが実際にスタックにプッシュされる前にスタックにプッシュされる構造に準備できます。関数の実装にジャンプします。ただし、コンパイラは関数の実際の実装を知る必要はありません。すべての関数呼び出しに対して、オブジェクトファイルに「プレースホルダー」を配置するだけです。(注:各cファイルは正確に1つのオブジェクトファイルにコンパイルされます)。#includesimpleはヘッダーファイルを取得し、その行をファイルの内容に置き換え#includeます。

コンパイル後、ビルドスクリプトはすべてのオブジェクトファイルをリンカーに渡します。リンカは、関数実装の物理的な場所を見つけるすべての関数の「プレースホルダー」を解決し、それらをオブジェクトファイル、フレームワークライブラリ、またはdllの中に入れます。これは、関数の実装を見つけることができる情報をすべての関数呼び出しに配置するだけなので、プログラムは、関数呼び出しに到達したときに実行を続行する場所を認識します。

以上のことをすべて言っても、ヘッダーファイルに関数定義を入れられない理由は明らかです。後で#includeこのヘッダーを複数のcファイルに挿入すると、両方が関数の実装を2つの別々のオブジェクトファイルにコンパイルします。コンパイラーはうまく機能しますが、リンカーがすべてをリンクしたい場合、関数の2つの実装が検出され、エラーが発生します。

stdlib.hと友達は同じように働きます。それらで宣言された関数の実装は、コンパイラがコードに「自動的に」リンクしているフレームワークライブラリにあります。

于 2012-06-22T13:50:26.730 に答える
3

C言語(C ++と共に)は、コンパイラーに他の場所で定義された関数を認識させるために、かなり時代遅れの戦略を使用します。

この戦略は次のようになります。関数などの署名(これはCでは宣言と呼ばれます)はヘッダーと呼ばれる特別なファイルに入れられ、それらを使用したい他のすべてのファイルは、ほぼ文字通りそのヘッダーをファイルに含めることが期待されます(実際には、#includedirectiveはコンパイラーにヘッダーのリテラルテキストを含めるように指示するだけです)。これにより、コンパイラーは関数宣言を再度確認できます。

他の言語は、この問題を別の方法で解決します。コンパイラはすべてのソースコードを確認し、コンパイル済みのクラス自体のメタデータを記憶します。

Cで使用される戦略は、すべての依存関係を見つけるタスクをコンパイラーから開発者に移します。これは、コンピューターが小さく、ばかげていて、遅い昔からの遺産であるため、開発者からのこの種の支援は非常に貴重でした。

この戦略には多くの欠点があり、理論的には今すぐ変更することが可能ですが、ギガバイトのコードがすでにそのスタイルで記述されているため、標準は変更されません。

tl; dr:70年代からの遺産です。

于 2012-06-22T13:32:53.920 に答える
1

Cでは、関数を呼び出す前に関数を宣言する必要があります。これが必要な理由は、70年代には、最初にファイルのすべてのシンボルを解析してから、もう一度解析して実際にコードをコンパイルするのに時間がかかりすぎるためでした。呼び出される前にすべての関数が宣言されている場合は、1回の解析で十分です。ただし、最新のシステムでは、これらの制限に直面しなくなりました。これが、モンダーン言語にこの要件がない理由です。

プロジェクトに2つのファイルa.c b.cがあるとします。foo両方のファイルで使用したい関数を実装します。関数を呼び出す前に関数を宣言する必要があるため、で関数を定義してa.c使用することはできません。b.cしたがって、に行void foo();を追加しb.cます。ただし、関数のシグネチャを変更するa.cたびに、の宣言を変更する必要がありますb.c。この問題を回避するには、ファイルが実装するすべての機能を個別のヘッダーファイル(この場合)で宣言するのがCの標準的な戦略ですa.h。ヘッダーファイルは、そのコードを使用する他のすべてのファイルにインクルードされます(したがって、これb.cを使用します#include "a.h":) 。

于 2012-06-22T13:43:42.723 に答える
0

An#includeは、ファイルが発生した場所にテキストで挿入されるようにするプリプロセッサディレクティブです#include

同じヘッダーファイルを含む複数の.cファイルをリンクする場合は、ヘッダーファイルが複数含まれないように注意する必要があります(テキストでヘッダーファイルを複数回挿入する)。、、、およびプリプロセッサディレクティブを使用して、複数の包含を防ぐことができます#ifndef#define#endif

#ifndef MY_FILE_H
#define MY_FILE_H

/* This code will not be included more than once. */

#endif /* !MY_FILE_H */
于 2012-06-22T14:03:03.200 に答える
0

コードが別のファイルにある場合、なぜヘッダーファイルを追加する必要があるのか​​理解できません。

ヘッダーファイルには、他のファイルで定義されている関数の宣言が含まれています。これは、関数を呼び出しているコードが正しくコンパイルされるために必要です。

たとえば、次のコードを記述したとします。

int main(void)
{
  double *foo = malloc(sizeof *foo * 10);
  if (foo)
  {
    // do something with foo
    free (foo);
  }
  return 0;
}

mallocは、メモリを動的に割り当て、そのメモリへのポインタを返す標準ライブラリ関数です。の戻り型mallocは、でありvoid *、その任意の値を他のポインタ型に割り当てることができます。 freeは、を介して割り当てられたメモリの割り当てを解除する別の標準ライブラリ関数でmallocあり、その戻りタイプはvoid(IOW、戻り値なし)です。

ただし、コンパイラーは、何を自動的に認識したり、何を返すか(または返さないかmallocfreeを認識しません。関数呼び出しを正しく変換する前に、現在のスコープ内の両方の関数の宣言を確認する必要があります。C89標準以前では、スコープ内の宣言なしで関数が呼び出された場合、コンパイラーは関数がint;を返すと想定します。intと互換性がないためdouble *(キャストなしで一方を他方に直接割り当てることはできません)、「互換性のない割り当て」診断が表示されます。C99以降では、暗黙の宣言はまったく許可されていません。いずれにせよ、コンパイラはコードを変換しません。

行を追加する必要があります

#include <stdlib.h>

これには、ファイルの先頭へのand (およびその他の多くのもの) の宣言が含まれます。mallocfree

関数定義(または変数定義)をヘッダーファイルに入れたくない理由はいくつかあります。fooヘッダーで関数を定義するとします。aha.hファイルa.cとにインクルードしますb.c。各ファイルは個別に正常にコンパイルされますが、ライブラリまたは実行可能ファイルを構築するためにそれらをリンクしようとすると、リンカーから「複数の定義」エラーが発生します。同じ名前、それはノーノーです。変数の定義 についても同じことが言えます。

また、スケーリングもうまくいきません。多数の関数を独自のヘッダーファイルに配置し、それらを1つのソースファイルに含めると、それらすべての関数を1つの大きなグロブに変換することになります。さらに、ソースファイルまたは1つのヘッダーファイルのコードのみを変更した場合でも、.cファイルを再コンパイルするたびにすべてを再コンパイルすることになります。各関数を独自の.cファイルに配置することで、再コンパイルが必要なファイルを再コンパイルするだけで、全体的なビルド時間を短縮できます。

于 2012-06-22T15:36:06.457 に答える