web-dev-qa-db-ja.com

Cの同じディレクトリにある別のファイルから関数を呼び出す

私はCを学んでいますが、Javaのような高レベルのプログラミング言語で長い経験を持っています。

私はヘッダーファイルについて読んでいたので、それらをいじっていましたが、#include(同じディレクトリにある)なしで別のファイルから関数を呼び出すことができることに気付きました、どうすれば可能ですか?!そのように構成されているのはメイクファイル、リンカーですか?

2つのファイルがあります

_main.c
add.c
_

main.cはadd_cから関数add(int x,int y)を呼び出しますが、#。add.cを含める前に誤ってコンパイルし、機能しました!さらに混乱させるのは、#include add.cを追加すると、関数addで複数定義エラーが発生することです。

23
MohamedEzz

ここではいくつかの異なることが行われています。最初に、複数のファイルの基本的なコンパイルの仕組みについて説明します。

複数のファイルがある場合、重要なことは、関数の宣言と定義の違いです。この定義は、おそらく関数を定義するときに慣れているものです。次のように、関数の内容を書きます。

int square(int i) {
    return i*i;
}

一方、宣言を使用すると、関数が存在することがわかっていることをコンパイラーに宣言できますが、コンパイラーにそれが何であるかを伝えません。たとえば、次のように書くことができます

int square(int i);

そして、コンパイラは、関数「square」が他の場所で定義されていることを期待します。

相互運用する2つの異なるファイルがある場合(たとえば、関数 "square"がadd.cで定義されており、main.cでsquare(10)を呼び出す場合)、次のようにする必要があります。 doboth定義と宣言。まず、add.cで正方形を定義します。次に、main.cの先頭でdeclareします。これにより、コンパイラはmain.cをコンパイルするときに、他の場所で定義されている関数「square」があることを認識できます。次に、main.cとadd.cの両方をオブジェクトファイルにコンパイルする必要があります。これを呼び出すには

gcc -c main.c
gcc -c add.c

これにより、ファイルmain.oおよびadd.oが生成されます。コンパイルされた関数が含まれていますが、完全には実行できません。ここで理解する重要なことは、ある意味でmain.oが「不完全」であることです。 main.oをコンパイルするときに、関数「square」は存在するが、関数「square」はmain.o内で定義されていないことを伝えました。したがって、main.oには、関数「square」への一種の「ぶら下がり参照」があります。 「square」の定義を含む別の.o(または.soまたは.a)ファイルと組み合わせない限り、完全なプログラムにコンパイルされません。 main.oをプログラムにlinkしようとすると、つまり.

gcc -o executable main.o

コンパイラーは関数「square」へのぶら下がり参照をresolveしようとしますが、その定義は見つかりませんので、エラーが発生します。ただし、リンク時にadd.oを含めると(リンクはresolvingのプロセスです).oファイルを実行可能ファイルまたは。ファイル)、問題はありません。つまり.

gcc -o executable main.o add.o

だからそれは機能的にCファイル全体で関数を使用する方法ですが、スタイル的に、私があなたに示したのは「正しくない方法」です。私がやった唯一の理由は、「#include magic」に頼るよりも、何が起こっているのかを理解するのに役立つと思うからです。さて、main.cの最上部で使用したいすべての関数を再宣言しなければならない場合、物事が少し面倒になることに気づいたかもしれません。これが、Cプログラムが.h拡張子を持つ "headers"というヘルパーファイルを使用する理由です。 。ヘッダーの概念は、関数の宣言justwithoutそれらの定義。このように、add.cで定義された関数を使用してプログラムをコンパイルするために、使用しているすべての関数を手動で宣言する必要も、コードにadd.cファイル全体を含める必要もありません。代わりに、#。include add.hを使用できます。これには、add.cのすべての関数の宣言が含まれています。

ここで、#includeの復習:#includeは、あるファイルの内容を別のファイルに直接コピーするだけです。したがって、たとえば、コード

abc
#include "wtf.txt"
def

とまったく同じです

abc
hello world
def

wtf.txtに「hello world」というテキストが含まれていると仮定します。

したがって、add.cのすべての宣言をadd.hに入れると(つまり、.

int square(int i);

そして、main.cの先頭に、

#include "add.h"

これは、main.cの上部で関数「square」を手動で宣言した場合と機能的に同じです。

そのため、ヘッダーを使用する一般的な考え方は、必要なすべての関数を#includeするだけで自動的に宣言する特別なファイルを作成できるということです。

ただし、ヘッダーにはもう1つの一般的な用途もあります。 main.cが50の異なるファイルの関数を使用すると仮定しましょう。 main.cの上部は次のようになります。

#include "add.h"
#include "divide.h"
#include "multiply.h"
#include "eat-pie.h"
...

代わりに、多くの場合、これらのすべての#includesをmain.hヘッダーファイルに移動し、main.cから#include main.hを移動するだけです。この場合、ヘッダーファイルはtwoの目的を果たします。他のファイルに含まれる場合に使用するためにmain.cの関数を宣言し、andmainから含まれる場合にmain.cのすべての依存関係を含めます。 c。この方法で使用すると、依存関係のchainsも許可されます。 add.hを#includeすると、add.cで定義された関数を取得するだけでなく、add.cが使用する関数、および関数they使用など。

また、より微妙に、それ自身の.cファイルのヘッダーファイルを含めると、エラーを暗黙的にチェックします。たとえば、誤って正方形を定義した場合

double square(int i);

add.hでは、main.oが正方形のone定義を探し、add.oが提供するまで、通常は気付かないかもしれません。 もう1つ、互換性のないこれにより、リンク時にエラーが発生するため、ビルドプロセスの後半までエラーに気付かないでしょう。ただし、コンパイラにadd.cからadd.hを#includeすると、ファイルは次のようになります

#include "add.h"
int square(int i) {
    return i*i;
}

#include文の処理後は次のようになります

double square(int i);
int square(int i) {
    return i*i;
}

コンパイラはadd.cをコンパイルするときに気づき、それについて説明します。事実上、この方法で独自のヘッダーを含めることで、提供している機能のタイプを他のファイルに誤って広告することを防ぎます。

関数を宣言せずに使用できる理由

お気づきのとおり、場合によっては、宣言することなく、または宣言するファイルを含めずに、実際に関数を使用できます。これは愚かであり、誰もがこれが愚かであることに同意します。ただし、Cプログラミング言語(およびCコンパイラ)のレガシー機能であるため、最初に宣言せずに関数を使用する場合は、単に「int」型を返す関数であると想定します。したがって、実際には、関数を使用すると、その関数がまだ宣言されていない場合に「int」を返す関数として暗黙的に宣言されます。あなたがそれについて考えるなら、それは非常に奇妙な振る舞いであり、その振る舞いをするならコンパイラはあなたに警告するべきです。

ヘッダーガード

もう1つの一般的な方法は、「ヘッダーガード」の使用です。ヘッダーガードを説明するために、考えられる問題を見てみましょう。 2つのファイル、herp.cとderp.cがあり、それらがboth互いに含まれている関数を使用したいとしましょう。上記のガイドラインに従って、次の行を含むherp.hを作成できます。

#include "derp.h"

そして、次の行を含むderp.h

#include "herp.h"

考えてみると、#include "derp.h"はderp.hの内容に変換され、これにはさらに#include "herp.h"という行が含まれ、herpの内容に変換されます。 h、およびthatcontains ...などのように、コンパイラはインクルードを展開するだけで永遠に続行します。同様に、main.hに#herp.hとderp.hの両方が含まれ、herp.hとderp.hの両方にadd.hが含まれる場合、main.hでadd.hの2つのコピー、1つは#herp.hを含む結果として、もう1つはderp.hを含む結果として。だから、解決策は? 「ヘッダーガード」、つまり、ヘッダーが2回インクルードされるのを防ぐコード。たとえば、add.hの場合、これを行う通常の方法は次のとおりです。

#ifndef ADD_H
#define ADD_H

int sqrt(int i);
...
#endif

このコードは基本的に、プリプロセッサー(すべての「#XXX」ステートメントを処理するコンパイラーの部分)に「ADD_H」が既に定義されているかどうかを確認するよう指示しています。そうでない場合(if n def)、最初に「ADD_H」を定義します(このコンテキストでは、ADD_Hを定義する必要はありませんas何でも、定義されているかどうかにかかわらずブール値であり、ヘッダーの残りのコンテンツを定義します。ただし、ADD_Hが既に定義されている場合、#ifndefブロックの外側に何もないため、#includeこのファイルはnothingを実行します。そのため、特定のファイルに初めて含まれる場合にのみ、実際にそのファイルにテキストを追加するという考え方です。その後、#includeを使用しても、ファイルに追加のテキストは追加されません。 ADD_Hは、add.hがまだ含まれているかどうかを追跡するために選択する任意のシンボルです。ヘッダーごとに、異なるシンボルを使用して、ヘッダーがまだ含まれているかどうかを追跡します。たとえば、herp.hはおそらくADD_HではなくHERP_Hを使用します。 「ヘッダーガード」を使用すると、ファイルの重複コピーが含まれたり、#includeの無限ループが発生したりする上記の問題のいずれかが修正されます。

86
Jeremy Salwen

問題は、.cファイルを#includeingしてはいけないということです。

別のファイルで関数を使用するには、それを宣言する必要があります。通常、すべての.cファイル(main.cを除く)には、.cファイルで定義されているすべての関数を適切に宣言するヘッダー(.h)ファイルが関連付けられています。 declareは何回でも宣言できます(すべての宣言が同一である限り)が、definitionは1つだけです。

#include "add.c"を実行すると、add.cのテキストがmain.cに含まれ、main.ca definition(および副作用として宣言)がaddになります。次に、add.cを単独でコンパイルすると、_anotheraddの定義が作成されます。したがって、関数には2つの定義があり、どちらを使用するかわからないため、コンパイラーはおかしくなります。

#include "add.h"に変更すると、add.hは次のようになります。

#ifndef ADD_H
#define ADD_H

extern int add(int x, int y);

#endif /* ADD_H - Google "include guard" for more info about this trickery */

main.cにはaddの宣言があり、関数を使用できますが、adddefinitionはadd.cファイルにのみしっかりと存在するため、1回だけ存在し、コンパイルされます正しく。

4
Chris Lutz

異なるcプログラムから関数を呼び出す簡単な例を次に示します

メインプログラムにmain.cという名前を付け、function.cの機能をfunction.cとして保持するプログラムに名前を付けます。function.hというヘッダーファイルを作成しています。

main.c

#include"function.h"
int main()
{
     int a = sum(1,2);
     return a;
}

function.c

int function(int a,int b)
{
    return a+b;
}

function.h

int function(int,int);

コンパイルするには、以下のコマンドを使用します

g ++ main.c function.c -o main

ここで詳細な説明。メインプログラムでは、2つの数値を合計する関数を呼び出しました。メインプログラムの値1と2は、function.cへのアクセスポイントまたはブリッジを保持するヘッダーfunction.hを介してfunction.cの関数にフィードされました。

詳細については、以下のリンクを確認できます

http://www.cplusplus.com/forum/beginner/34691/

https://social.msdn.Microsoft.com/Forums/en-US/4ea70f43-a0d5-43f8-8e24-78e90f208110/calling-a-function-in-a-file-from-another-file?forum=winembplatdev

Printステートメントを追加して結果を確認するか、echo $?を使用しますファイルmainの実行後

2
Shameerariff

Cで呼び出しを行うのに宣言は必要ないため、呼び出すことができます。ただし、戻り値の型は不明であるため、デフォルトでintになります。これは、Cのデフォルトの呼び出し規則と、少なくともint精度への型のデフォルトの昇格が原因の1つです。

呼び出す関数を定義するヘッダーを含めると、コンパイラーは関数の呼び出しに正しい数とタイプの引数があることを確認できます。

関数定義を含める場合、staticでストレージを指定しない限り、それらはエクスポートされます。 add.c、オブジェクトファイルのどちらかまたは両方がaddをエクスポートしないため、これを追加することはできません。

すべての関数を単に含めたい場合、それらの定義をヘッダーに入れて、それらにストレージ指定子を振りかけるのが最善です。

1
Matt Joiner