プログラミング言語における外部コードの導入、構築、およびパッケージ管理メカニズムについてのある程度の理解

序文

少し前に、私はクラスメートと、サードパーティのコードを cpp に導入する方法について話し合いました。これには、モジュール化とパッケージ管理メカニズムが含まれていました。この部分の内容についてはあまり詳しくないのですが、前から調べておきたかったので、他の方にもわかりやすいように今月のブログでまとめてみます。


1. 外部コードの導入方法

ここに画像の説明を挿入

大規模なプロジェクトを作成する場合、標準ライブラリが提供する機能ではプロジェクトの要件を満たせない場合、サードパーティが提供するライブラリをプロジェクトに導入することが一般的です。外部ライブラリを導入する際に採用する方法は言語ごとに異なりますが、まとめると次の2種類があると思われます。

  1. ヘッダファイルとソースコードの形で紹介
  2. バイナリ ファイルおよびライブラリ ファイル (Linux の .o オブジェクト ファイルや Java の .class ファイルを含む) の形式で導入されます。

cpp、java、go 言語を例として Wave を分析してみましょう。

1.1 C/Cppで外部コードを導入する方法

ここに画像の説明を挿入

C および Cpp の場合、言語レベルでヘッダー ファイルとソース ファイルの概念があり、前者はシンボルの宣言を担当し、後者はシンボルの定義を担当します。この古い時代では、当時のメモリが比較的小さく (わずか数十 kb)、コンパイル時にソース コード ファイルをメモリに完全に格納できなかったため、このメソッドが考案されました。がヘッダー ファイルに配置され、大きなソース コード ファイルは個別にコンパイルするモジュラー コンパイルの原則に従って複数のコンパイル単位 (複数のソース コード ファイル) に分割され、最終的に実行可能ファイルにリンクされます。情報シールドの観点からは、宣言と定義を分離することは、「インターフェイスを公開し、詳細を隠す」という原則にも一致します。

外部コードを導入するときは、#include ヘッダー ファイルを使用して、外部コードの関連する宣言をコンパイル単位全体に含めます。前処理段階では、テキストをコピーするのと同様の方法で、外部コードの宣言をオブジェクト ファイルにコピーします。

ライブラリが「ヘッダーのみ」として提供されている場合、プログラムのビルド時に、インポートされた外部コードもコンパイル プロセス全体に参加します。これが上記の方法 1 です。

ライブラリにヘッダー ファイル以外のソース ファイルがある場合、インポートされたヘッダー ファイルはコンパイル段階で引き続き使用されます。また、通常は、外部ソース コード (.o ターゲット ファイルまたは静的ファイル) からコンパイルされたバイナリ ファイルを指定する必要があります。およびダイナミック ライブラリ ファイル)。これが上記2の方法です。

ちなみに、Linux にソフトウェア (openvpn など) をインストールする必要がある場合、openvpn 自体のコードをコンパイルしてインストールすることに加えて、openssl などの追加の依存ライブラリもインストールする必要がある場合があります (そうでないと、様々なリンクエラーが発生します)の理由は上記の通りです。

もちろん、外部ライブラリのソース ファイルとヘッダー ファイルを一緒に取得し、ローカル プロジェクトにマージし、ローカル プロジェクトの一部として構築に参加するという別の方法もあります。これは実際には上記の方法 1 に属しますが、この方法は小規模なプロジェクトには問題ありませんが、大規模なプロジェクトの場合はコンパイル時間と環境構成が少し過剰になる可能性があります。

1.1.1 cpp 20 のモジュールについて

C/CPP でヘッダー ファイルを使用する方法には、よく指摘されるいくつかの欠点があります。その最も重要な欠点は、構築の速度が低下することです。毎回ahを入れると、前処理の段階で一旦展開されてしまい、コンパイル時にソースファイルが肥大化して不快になってしまいます。新しい cpp20 標準定式化では、新しい機能モジュール メカニズムが追加されました。モジュール機構を使用した後は、外部コードを導入するときに、java や go のように、ヘッダー ファイルをインクルードすることなく直接インポートできます。

cpp には、統合された使いやすいパッケージ管理ツールが常に欠けていたもう 1 つのスロットがあります。その理由の 1 つは、ヘッダー ファイルをインポートするために include を使用する方法が cpp ライブラリ (パッケージ) とコンポーネントを使用しないことです。管理ツール。モグラの場合は、状況は徐々に改善されるはずです。

私はこの新しいモジュール機構を使ったことはありませんが、インターネット上の多くの人は、このモジュール機構によりコンパイルの効率が向上すると言っています。ただし、cpp の複雑で多様な構築システムを考えると、このモジュールが正式に成熟するまでには長い道のりが必要です。

1.2 Java に外部コードを導入する方法

ここに画像の説明を挿入

Java のライブラリを使用して関数、データ、クラスを整理します。サードパーティのクラス ライブラリを導入する場合は、対応するライブラリを直接インポートできます。これらのライブラリは jar パッケージの形式で提供されており、対応するライブラリを準備する必要がありますjar パッケージを使用する場合。これらの jar パッケージは、サードパーティのソース コードを使用してコンパイルされ、.class ファイルが生成されます。このようなファイルは、ある程度、コンパイルされた中間ファイルともみなされる必要があり、これは上記の 2 番目の方法に起因すると考えられます。


ここで少し話が逸れました。
Java はコンパイル言語ですか、それともインタプリタ言語ですか?

回答: 私の理解によれば、それはコンパイル言語とインタプリタ言語の両方としてみなされるべきです。ソースコードから.classファイルまでの処理はコンパイルによって実現され、.classファイルからターゲットマシンで実行可能なバイナリコードまではJava仮想マシンを使用して解釈されます。

1.3 Go 言語で外部コードを導入する方法

ここに画像の説明を挿入

Javaと同様に、Goでもサードパーティのパッケージ(ライブラリ)をインポートする場合もimportを使用しますが、違いは、Go言語で導入されたパッケージは主にサードパーティのソースコード、つまりサードパーティのソースコードの形式でインポートされることです。ローカル コードと一緒に最終的なバイナリ プログラムにコンパイルされます。ソースコードライブラリのような形でサードパーティのコードを参照しているからこそ、一般的なgo言語プログラムにとって再コンパイルは飲食のようなものであり、あまりにも日常的すぎる。冒頭で述べたことに対応して、Go が外部コードを導入する方法は、上記の方法 2 に属します。コンパイル速度の問題を解決するために、Go 言語では、コンパイルを高速化するために循環参照を許可しないなど、設計中に多くのトレードオフも行われています。

もちろん、正確に言えば、Go ではコードを静的ライブラリにコンパイルして配布するバイナリ方式も使用できますが、この方式はほとんど使用されないため、ここでは触れません。

2. 構築およびパッケージ管理の仕組み

いわゆる構築とは、ソース コードからバイナリの実行可能プログラムまでのプロセスを指します。私の理解では、パッケージ管理メカニズムは、エンジニアリング プロジェクト全体のライブラリの依存関係管理とバージョン管理を処理し、それをビルド ツールと組み合わせて、より便利に実行可能プログラムにコンパイルすることです。java、go、javascriptなど1990年代以降に登場した言語は、いずれも構築やパッケージ管理の仕組みが比較的整っていますが、少し古いプログラミング言語であるC/Cppはまだ成熟していません。ビルドとパッケージの管理メカニズム。

以下では、引き続き Cpp、Java、および Go を例として取り上げ、それらが説明する構築およびパッケージ管理メカニズムを簡単に紹介します。

2.1 Cppの構築とパッケージ管理の仕組み

ここに画像の説明を挿入

Cpp は比較的最下層に近いプログラミング言語であるため、その構築プロセスはオペレーティング システムとマシン アーキテクチャ (x86、arm など) に関連しており、一貫性のないコンパイラやモジュール メカニズムの欠如などと相まって、構築とパッケージ管理メカニズムはより複雑になります。

現在、Cpp の主流のコンパイラは主に GCC、Clang、MSVC です。比較的小規模なデモの場合は、手動コンパイルで構築できます。

もう少し大きなシステムの場合は、特別なビルド ツールが必要ですが、現在、主なビルド システムには Make、GNU Autotools、ninja などがあります。よく使われるのは Make です。Make のルールに従って、Makefile ファイルを作成し、最後のステップは Make で構築プロセス全体を自動的に実行します。

もちろん、Makefile の作成も少し複雑で、機械的であり、特により複雑な依存関係を持つプロジェクトの場合はエラーが発生しやすくなります。Makefile の作成を簡素化し、複数のプラットフォーム アーキテクチャに適応するために、一部の先人は、ユーザーが Makefile を作成できるようにメタ ビルド システムを開発しました。メタビルド システムはビルド操作を実行しませんが、より高いレベル (CMakeLists.txt テキストの CMake など) からビルドの依存関係を記述し、それらを Makefile などの基礎となるビルド システムに変換します。システム プラットフォーム関連の依存関係をシールドするため、メタビルド システムは優れたクロスプラットフォーム機能を備えています。現在、主流のソース ビルド システムは CMake、gn、QMake などです (私の連絡範囲によると、CMake はが多く使われます)

上記は Cpp の構築プロセスです。パッケージ管理ツールについては、よく知られている理由により、Cpp には現在統一的なパッケージ管理メカニズムがありません。現在、cnona、vcpkg などが広く使用されていますが、それぞれにまだ機能がありません。統一された市場パターンはまだ形成されていません。詳細なパッケージ管理についてはここでは繰り返しませんので、興味のある方は [7] を参照してください。

2.2 Javaの構築とパッケージ管理の仕組み

ここに画像の説明を挿入
一般に、手動で構築される Java プロジェクトは比較的少なく、Java 構築エコロジーが非常に優れているため、実際の使用では、プロジェクトに基づいて特別な構築ツールやパッケージ管理メカニズムが使用されるのが一般的です。Ant と Maven はどちらも Java ベースのビルド ツールであり、前述の Make に似ています。Antはソフトウェア構築ツールであり、Mavenはソフトウェアプロジェクトの管理および理解ツールとして位置付けられています。

えんえん…、細かすぎてよくわかりません╮(╯▽╰)╭。

2.3 Go のビルドとパッケージ管理メカニズム

ここに画像の説明を挿入

新しい時代の C 言語として、Go の設計初期の構築およびパッケージ管理メカニズムは Cpp ほど煩雑で複雑ではありません (ただし、最初はあまり良くありませんでした)。Google は指定された唯一の公式であるため、そのパッケージ管理メカニズムは言語自体に近いものになっています (ただし、パッケージ管理ツールは外部にあります)。インターネットによると、その構築とパッケージ管理メカニズムはおそらく 3 つの段階を経たものと考えられます。

  • GOPATH フェーズ
    このメカニズムのすべてのパッケージは GOPATH/src パスに配置され、go get を使用して目的のパッケージを GOPATH パスにダウンロードできます。プロジェクトを開始すると、インポートに応じて対応するパッケージが自動的に取得されますが、バージョンの区別はサポートされておらず、効率的なバージョン管理ができません。

  • Go Vender フェーズ
    Go1.5 バージョンで govender を導入し、Go1.7 で強制起動します。これは、ベンダーのメカニズムに基づいて Go パッケージの依存関係を管理するためのコマンドライン ツールです。これにより、同じ外部ライブラリ上の異なるプロジェクトの依存関係が分離され、異なるプロジェクトが独自の独立した依存関係パッケージを使用するという問題が効果的に解決されます。コンパイル時の検索パスは /vendor で始まり、次に GOPATH、最後に GOROOT になります。

    ただし、この方法ではある程度の冗長性が生じるため、同じライブラリを異なるプロジェクトで使用する場合は、対応する /vendor に別のコピーをコピーする必要があります。

  • Go Modules 段階
    Go1.11 以降、Go Modules メカニズムは正式に廃止され、このメカニズムのすべての依存パッケージは GOPATH/pkg/mod ディレクトリに配置され、生成されたバイナリ ファイルは GOPATH/bin ディレクトリに配置されます。エンジニアリング プロジェクト全体を GOPATH から独立させて他の場所に配置することもできますが、バージョン情報を記述するためにプロジェクト内に go.mod ファイルが必要です。

    最初のメソッド GOPATH と同様に、このメソッドも依存パッケージの場所を指定しますが、外部依存関係を使用しているプロジェクトのバージョンが go.mod に記録されるため、プロジェクト間での依存パッケージの再利用を実現するだけでなく、異なるプロジェクトの問題 同じ外部パッケージの異なるバージョンへの依存関係。

    現在、この方法は主にパッケージ管理に使用されています。

追記

さて、最後に一つ質問ですか?

Java js が仮想マシン上で実行され、これにより便利なパッケージ管理メカニズムが提供されるのに、go は仮想マシン上で実行されないのに、なぜ実行できるのでしょうか? なぜ cpp にはこれに対する適切なメカニズムがないのでしょうか?

どう思いますか?


参考

[1] C++20 の 4 つの主要機能の 1 つ: モジュール機能の詳細な説明
[2] C++ は他人が作成したライブラリをどのように使用するのですか? ライブラリ ファイルをインクルードするには include コマンドも使用しますか?
[3] C++20 が登場します!
[4] go と java の依存性注入実装のいくつかの違い
[5]どちらもシステムレベルのプログラミング言語である Rust には最新のビルド/パッケージ管理ツールがあるのに、C++ にはできないのはなぜですか?
【6】C/C++ビルドシステム入門
【7】オープンソースのC/C++パッケージ管理ツールのスタックをパッケージ化!
[8] Ant と Maven の違い
[9] Golang のパッケージ管理 - go モジュール
[10] go の 3 つのパッケージ管理方法
[11] C++20 - 次期メジャーバージョンの機能が確認
[12] C++20 New機能: モジュールと実装状況
[13] C++20 モジュール システム
[14] C、C++、Java、Python、Go、Rust、Dart ヘッダー ファイル、ライブラリ、パッケージ、モジュール
[15] Go: パッケージ管理ツール GOPATH 、ベンダー、dep、goモジュール

おすすめ

転載: blog.csdn.net/plm199513100/article/details/124567840