10

私がオンラインで見るすべてのコードでは、プログラムは常に多数の小さなファイルに分割されています。とはいえ、学校での私のすべてのプロジェクトでは、私が使用するすべての構造体と関数を含む 1 つの巨大な C ソース ファイルを用意するだけで済みました。

私が学びたいのは、プログラムをより小さなファイルに分割することです。これは、専門的には標準のようです。(ちなみに、これはなぜですか?読みやすくするためですか?)

私は周りを検索しましたが、ライブラリの構築に関する情報しか見つかりませんでした。これは私がやりたいことではないと思います。もっと役に立ちたいと思っていますが、これを実装する方法については完全にはわかりません.最終製品が欲しいだけです.

4

6 に答える 6

9

コードをいくつかのライブラリに分割してください。

あなたが持っている1つのファイルで例を見てみましょう:

#include <stdio.h>

int something() {
    return 42;
}

int bar() {
    return something();
}

void foo(int i) {
    printf("do something with %d\n", i);
}

int main() {
    foo(bar());
    return 0;
}

これを次のように分割できます:

mylib.h:

#ifndef __MYLIB_H__
#define __MYLIB_H__

#include <stdio.h>

int bar();
void foo();

#endif

注意: 上記のプリプロセッサ コードは「ガード」と呼ばれ、このヘッダー ファイルを 2 回実行しないようにするために使用されるため、複数の場所で同じインクルードを呼び出すことができ、コンパイル エラーは発生しません。

mylib.c:

#include <mylib.h>

int something() {
    return 42;
}

int bar() {
    return something();
}

void foo(int i) {
    printf("do something with %d\n", i);
}

myprog.c:

#include <mylib.h>
int main() {
    foo(bar());
    return 0;
}

それをコンパイルするには:

gcc -c mylib.c -I./
gcc -o myprog myprog.c -I./ mylib.o

今の利点は?

  1. コードを論理的に分割し、コード単位をより速く見つけることができます
  2. これにより、コンパイルを分割し、何かを変更したときに必要なものだけを再コンパイルできます (これが Makefile の役割です)。
  3. 一部の関数を公開し、他の関数を非表示にすることができます (上記の例の「something()」など)。また、コードを読む人 (教師など) のために API を文書化するのに役立ちます;)
于 2012-04-12T14:34:53.283 に答える
4

読みやすくするためだけですか?

理由は

  • 保守性:あなたが説明するような大規模なモノリシックプログラムでは、ファイルの一部のコードを変更すると、他の場所で意図しない影響が生じる可能性があるというリスクがあります。私の最初の仕事に戻って、私たちは3Dグラフィック表示を駆動するコードを高速化するという任務を負いました。それは単一のモノリシックな5000行以上のmain関数であり(壮大な計画ではそれほど大きくはありませんが、頭痛の種になるほどの大きさです)、私たちが行ったすべての変更はどこか別の場所で実行パスを壊しました。これはずっとひどく書かれたコードでした(gotoたくさんの、文字通り何百もの別々の変数と次のような信じられないほど有益な名前nv001x、昔ながらのBASICのように読み取るプログラム構造、何もしなかったマイクロ最適化、コードを読みにくくし、地獄のように壊れやすい)が、すべてを1つのファイルに保持すると、悪い状況が悪化しました。最終的にはあきらめて、すべてを最初から書き直すか、より高速なハードウェアを購入する必要があることをお客様に伝えました。彼らはより速いハードウェアを購入することになった。

  • 再利用性:同じコードを何度も書くことに意味はありません。一般的に有用なコード(たとえば、XML解析ライブラリや汎用コンテナなど)を思いついた場合は、それを個別にコンパイルされた独自のソースファイルに保持し、必要に応じてリンクするだけです。

  • テスト容易性:関数を独自の個別のモジュールに分割すると、残りのコードから分離してそれらの関数をテストできます。個々の機能をより簡単に確認できます。

  • 構築可能性:わかりました。「構築可能性」は本当の言葉ではありませんが、1行または2行を変更するたびにシステム全体を最初から再構築するのは時間がかかる場合があります。私は、完全なビルドに数時間以上かかる可能性がある非常に大規模なシステムに取り組んできました。コードを分割することで、再構築する必要のあるコードの量を制限できます。言うまでもなく、コンパイラには、処理できるファイルのサイズにいくつかの制限があります。私が上で述べたそのグラフィカルドライバー?それを高速化するために最初に試みたのは、最適化をオンにしてコンパイルすることでした(O1から開始)。コンパイラは利用可能なすべてのメモリを使い果たし、カーネルがパニックに陥ってシステム全体を停止するまで、利用可能なすべてのスワップを使い果たしました。私たちは文字通り構築できませんでした最適化がオンになっているコード(これは、128 MBが非常に高価なメモリであった時代にさかのぼります)。そのコードが複数のファイル(地獄、同じファイル内の複数の関数)に分割されていれば、その問題は発生しなかったでしょう。

  • 並列開発:これには「能力」という言葉はありませんが、ソースを複数のファイルとモジュールに分割することで、開発を並列化できます。私は1つのファイルで作業し、あなたは別のファイルで作業し、他の誰かが3番目のファイルで作業します。そのようにお互いのコードを踏むリスクはありません。

于 2012-04-12T15:33:15.997 に答える
3

読みやすさのためだけですか?

いいえ、コンパイルの時間を大幅に節約することもできます。1 つのソース ファイルを変更すると、すべてを再コンパイルするのではなく、そのファイルのみを再コンパイルしてから再リンクします。しかし、要点は、プログラムを、単一のモノリシックな「blob」よりも理解しやすく保守しやすい、適切に分離されたモジュールのセットに分割することです。

手始めに、「データが支配する」structというロブ・パイクのルールに従うようにしてください。つまり、一連のデータ構造 (通常は ) を操作するようにプログラムを設計します。単一のデータ構造に属するすべての操作を別のモジュールに入れます。staticモジュール外の関数によって呼び出される必要のないすべての関数を作成します。

于 2012-04-12T14:29:16.850 に答える
2

読みやすさはファイルを分割するポイントの 1 つですが、複数のファイル (ヘッダー ファイルとソース ファイル) を含むプロジェクトをビルドする場合、優れたビルド システムは変更されたファイルのみを再ビルドするため、ビルド時間が短縮されます。

モノリシック ファイルを複数のファイルに分割する方法については、さまざまな方法がありますたとえば、すべての入力処理を 1 つのソース ファイルに配置し、出力を別のソース ファイルに配置し、さまざまな関数で使用される関数を 3 番目のソース ファイルに配置します。構造体/定数/マクロ、グループ関連の構造体などでも同じことをします。個別のヘッダー ファイルで。また、1 つのソース ファイルでのみ使用される関数を としてマークしstatic、誤って他のソース ファイルから使用されないようにします。

于 2012-04-12T14:32:57.447 に答える
1

ええと、私は専門家ではありませんが、関数よりも大きなエンティティで常に考えようとしています。論理的に一緒に属する関数のグループがある場合は、それを別のファイルに入れます。通常、機能が似ていて、そのような機能の 1 つが必要な場合は、おそらくこのグループの他の機能も必要になるでしょう。

単一のファイルを分割する必要があるのは、ファイルに異なるフォルダーを使用しているのと同じ理由によるものです。人々は、多数の関数を論理的に整理して、巨大な単一のソース ファイルを grep する必要がないようにしたいと考えています。必要なものを見つける。このようにして、プログラムの固定部分について考えたり開発したりするときに、プログラムの無関係な部分を忘れることができます。

分割のもう 1 つの理由は、ヘッダーで言及しないことで、一部の内部関数をコードの残りの部分から隠すことができることです。このようにして、内部関数 (ファイル内でのみ必要.c) を、プログラムの外部の「宇宙」にとって興味深い関数から明示的に分離します。

より高水準の言語の中には、「一緒に属する関数」の概念を「1つのエンティティとして提示された同じものに作用する関数」に拡張し、それをクラスと呼んだものもあります。

分割のもう 1 つの歴史的な理由は、個別のコンパイル機能です。コンパイラが遅い場合 (たとえば、C++ ではよくあることです)、コードを複数のファイルに分割すると、1 つの場所だけを変更すると、変更を反映するために 1 つのファイルだけを再コンパイルする必要がある可能性が高くなります。 . 最新の C コンパイラは一般的なプロセッサ速度と比較してそれほど遅くないため、これは問題にならないかもしれません。

于 2012-04-12T14:26:57.877 に答える