C言語で動的リンク,静的リンク,動的ロードの違いを教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Assembler (x64,ARM).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
こういった私から学べます.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!
本記事ではC言語で動的リンク,静的リンク,動的ロードの違いを紹介します.
どれも似ている方式ですが,本記事を読むと違いがわかります.
目次
【C言語】動的リンク
動的リンクとは,OSの一部で,プログラムの実行時(ランタイム)に実行ファイルに必要な共有ライブラリをロードおよびリンクする方式です.
ライブラリの内容を永続記憶装置(HDD/SSD等)からRAMにコピーし,ジャンプテーブルを埋め,ポインタを再配置します.
動的リンクを利用したライブラリのことを動的リンクライブラリ(ダイナミックリンクライブラリ)と呼びます.
動的リンクライブラリは,主に共有ライブラリ(.soの拡張子のファイル)になります.
動的リンクを利用することで,プログラムのファイルサイズを小さくすることができますが,プログラムの実行時に結合するため起動が遅くなります.
ただ,この起動の遅さは現在のコンピュータ環境ではほとんど問題にならないため,動的リンクを利用することが多いです.
共有ライブラリの作り方は,以下のように-sharedオプションをつけてオブジェクトファイル(a.oとb.o等)をlibmyfunc.soに出力します.
※libXX.soというファイル名の命名規則は守るようにして下さい.この例ではXXはmyfuncです.
1 |
$ gcc -shared a.o b.o -o libmyfunc.so |
また,共有ライブラリのリンク方法は,主に以下の3種類になります.
1 |
$ gcc -o main main.c libmyfunc.so |
1 |
$ gcc -o main main.o libmyfunc.so |
1 |
$ gcc -o main main.o -lmyfunc -L . |
特に3番目の方法がよく利用されますが,libmyfunc.soというファイル名に対して,「-lmyfunc -L .」という形に変わるので注意して下さい.
「-L .」はカレントディレクトリで共有ライブラリを検索する指示をしていて,「-lmyfunc」は「libmyfunc.so」ファイルを発見したらリンクする指示になります.
※libmyfunc.soから先頭のlibと-lをスワップし,拡張子の.soを削除したものが「-lmyfunc」という感じで覚えましょう.
C言語で動的リンクをするコード一式はこちらからダウンロードして下さい.
- Makefile:ビルド構成ファイル
- main.c:main関数を実装
- printf.c:printf関数を呼びだすcall_printf関数を実装
- puts.c:puts関数を呼びだすcall_puts関数を実装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# # Makefile for Dynalic Link # # Author: Hiroyuki Chishiro # CC = gcc RM = rm -f LDSRCS = printf.c puts.c LDOBJS = $(LDSRCS:.c=.o) LDTARGET = libmyfunc.so CFLAGS += -Wall LDFLAGS = -l$(subst lib,,$(LDTARGET:.so=)) -L . SRCS = main.c OBJS = $(SRCS:.c=.o) TARGET = main .PHONY: all clean all: $(TARGET) $(TARGET): $(OBJS) $(LDOBJS) $(CC) -shared $(LDOBJS) -o $(LDTARGET) $(CC) -o $@ $(OBJS) $(LDFLAGS) $(OBJS): $(SRCS) $(LDSRCS) $(CC) $(CFLAGS) -c $< clean: $(RM) -r $(TARGET) $(LDTARGET) $(OBJS) $(LDOBJS) *~ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> void call_puts(const char *str); void call_printf(const char *str); int main(void) { call_puts("puts()\n"); call_printf("printf()\n"); return 0; } |
1 2 3 4 5 6 7 8 9 10 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> void call_printf(const char *str) { printf("%s", str); } |
1 2 3 4 5 6 7 8 9 10 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> void call_puts(const char *str) { puts(str); } |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 11 |
$ make gcc -Wall -c main.c gcc -Wall -c -o printf.o printf.c gcc -Wall -c -o puts.o puts.c gcc -shared printf.o puts.o -o libmyfunc.so gcc -o main main.o -lmyfunc -L . $ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $ ./main puts() printf() |
5行目の「export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH」は,ライブラリのパスにカレントディレクトリを追加する処理です.
「export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH」を実行せずに「./main」と実行すると以下のようなエラーが発生しますので注意して下さい.
1 2 |
$ ./main ./main: error while loading shared libraries: libmyfunc.so: cannot open shared object file: No such file or directory |
「export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH」をあなたのシェルの設定ファイル(.bashrc,.zshrc等)に登録しておくと簡単です.
【C言語】静的リンク
静的リンクは,ルーチン,外部関数,変数のセットであり,コンパイル時に呼び出し側で解決され,コンパイラやリンカによってターゲットアプリケーションにコピーされ,オブジェクトファイルとスタンドアロンの実行可能ファイルを生成する方式のことです.
静的リンクを利用したライブラリのことを静的リンクライブラリ(スタティックリンクライブラリ)と呼びます.
静的リンクライブラリは,アーカイブ(.aの拡張子のファイル)になります.
動的リンクと比較する場合,静的リンクを利用することで,プログラムのファイルサイズは大きくなりますが,プログラムの実行時の起動が速くなります.
ただ,このプログラムのファイルサイズの大きさは現在のコンピュータ環境ではほとんど問題にならないため,動的リンクを利用することが多いです.
また,静的リンクすることで,違うマシンでも特定のバージョンのライブラリを利用することができます.
動的リンクは実行時にリンクするので,マシン毎にライブラリのバージョンが異なる可能性があり,関数の互換性やバグの影響で実行時にエラーが発生する可能性があります.
静的リンクライブラリの作り方は,以下のようにarコマンドでrオプションをつけてオブジェクトファイル(a.oとb.o等)をlibmyfunc.aに出力します.
※libXX.aというファイル名の命名規則は守るようにして下さい.この例ではXXはmyfuncです.
1 |
$ ar r libmyfunc.a a.o b.o |
C言語で静的リンクをするコード一式はこちらからダウンロードして下さい.
動的リンクのコード一式とは,Makefileのみが異なります(C言語のコードは同じ).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# # Makefile for Static Link # # Author: Hiroyuki Chishiro # CC = gcc AR = ar RM = rm -f LDSRCS = printf.c puts.c LDOBJS = $(LDSRCS:.c=.o) LDTARGET = libmyfunc.a CFLAGS += -Wall LDFLAGS = SRCS = main.c OBJS = $(SRCS:.c=.o) TARGET = main .PHONY: all clean all: $(TARGET) $(TARGET): $(OBJS) $(LDOBJS) $(AR) r $(LDTARGET) $(LDOBJS) $(CC) -o $@ $(OBJS) $(LDTARGET) $(LDFLAGS) $(OBJS): $(SRCS) $(LDSRCS) $(CC) $(CFLAGS) -c $< clean: $(RM) -r $(TARGET) $(LDTARGET) $(OBJS) $(LDOBJS) *~ |
実行結果は以下になります.
1 2 3 4 5 6 7 8 9 10 11 |
$ make gcc -Wall -c main.c gcc -Wall -c -o printf.o printf.c gcc -Wall -c -o puts.o puts.c ar r libmyfunc.a printf.o puts.o ar: creating libmyfunc.a gcc -o main main.o libmyfunc.a $ ./main puts() printf() |
【C言語】動的ロード【dlopen/dlerror/dlsym/dlclose関数】
1 2 3 4 |
void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle); |
dlopen/dlerror/dlsym/dlclose関数は,動的リンクを行うローダ(loader)へのインターフェースを実装したものです.
dlopen関数は,文字列filenameで指定されたファイル名の動的ライブラリ (dynamic library) をロードし,その動的ライブラリへの内部「ハンドル」を返します.filenameがNULLの場合,メインプログラムへのハンドルが返されます.
第2引数flagは,RTLD_LAZYまたはRTLD_NOWのどちらかを設定する必要があります(詳細は上記のリンク先を参照).
dlerror関数は,前回dlerror関数が呼び出された後に,dlopen/dlsym/dlclose関数のいずれかで最後に発生したエラーについての説明メッセージを返します.
初期化後または前回呼び出された後で,エラーが発生していなければNULLを返します.
dlsym関数は,dlopen関数が返した動的ライブラリの「ハンドル」と,NULL終端されたシンボル名の文字列を引き数に取り,そのシンボルがロードされたメモリのアドレスを返します.
シンボルが,指定されたライブラリと,指定されたライブラリがロードされる際にdlopen関数が自動的にロードしてライブラリのいずれにも見つからない場合には,dlsym関数はNULLを返します.
dlclose関数は動的ライブラリのハンドルhandleの参照カウントを1減らします.
参照カウントが0になり,ロードされている他のライブラリからそのライブラリ内のシンボルが使われていなければ,その動的ライブラリをアンロードします.
dlclose関数は,成功した場合は0を返し,エラーの場合は0以外を返します.
C言語で動的ロードをするコード一式はこちらからダウンロードして下さい.
print.cとputs.cは動的リンクや静的リンクのコードと同様で,Makefileとmain.cが異なります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
# # Makefile for Dynalic Load # # Author: Hiroyuki Chishiro # CC = gcc RM = rm -f LDSRCS = printf.c puts.c LDOBJS = $(LDSRCS:.c=.o) LDTARGET = libmyfunc.so CFLAGS += -Wall LDFLAGS = -rdynamic -ldl SRCS = main.c OBJS = $(SRCS:.c=.o) TARGET = main .PHONY: all clean all: $(TARGET) $(TARGET): $(OBJS) $(LDOBJS) $(CC) -shared $(LDOBJS) -o $(LDTARGET) $(LDFLAGS) $(CC) -o $@ $(OBJS) $(OBJS): $(SRCS) $(LDSRCS) $(CC) $(CFLAGS) -c $< clean: $(RM) -r $(TARGET) $(LDTARGET) $(OBJS) $(LDOBJS) *~ |
main関数では,動的ロードのdlopen/dlerror/dlsym/dlclose関数が呼ばれていること,call_puts/call_printf関数は直接呼び出さずに31~32行目で関数ポインタとして呼び出されていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/* * Author: Hiroyuki Chishiro * License: 2-Clause BSD */ #include <stdio.h> #include <stdlib.h> #include <dlfcn.h> int main(void) { void *handle; void (*call_puts)(const char *); void (*call_printf)(const char *); char *ret; if ((handle = dlopen("libmyfunc.so", RTLD_LAZY)) == NULL) { fprintf(stderr, "%s\n", dlerror()); exit(1); } dlerror(); *(void **) &call_puts = dlsym(handle, "call_puts"); *(void **) &call_printf = dlsym(handle, "call_printf"); if ((ret = dlerror()) != NULL) { fprintf(stderr, "%s\n", ret); exit(2); } (*call_puts)("puts()\n"); (*call_printf)("printf()\n"); if (dlclose(handle) != 0) { fprintf(stderr, "%s\n", dlerror()); exit(3); } return 0; } |
実行結果は以下になります.
6行目の通り,libmyfunc.soは実行ファイル「main」を作成する時にリンクしていません.
ですが,動的ロードをすることで9~11行目で「puts()」や「printf()」が正常に表示されていることがわかります.
1 2 3 4 5 6 7 8 9 10 11 |
$ make gcc -Wall -c main.c gcc -Wall -c -o printf.o printf.c gcc -Wall -c -o puts.o puts.c gcc -shared printf.o puts.o -o libmyfunc.so -rdynamic -ldl gcc -o main main.o $ export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $ ./main puts() printf() |
動的リンク,静的リンク,動的ロードの違い
動的リンク,静的リンク,動的ロードの違いをまとめると下表になります.
現在のコンピュータ環境ではプログラムのファイルサイズやプログラムの起動時やロード時の速度は気にならないレベルですので,動的リンクがよく利用されます.
項目 | 動的リンク | 静的リンク | 動的ロード |
---|---|---|---|
ライブラリをリンク・ロードする タイミング | 実行時 (main関数の呼び出し前) | コンパイル時 | 実行時 (main関数の呼び出し後) |
プログラムのファイルサイズ | 小さい | 大きい | 小さい |
プログラムの起動時や ロード時の速度 | 遅い | 速い | 遅い |
まとめ
C言語で動的リンク,静的リンク,動的ロードの違いを紹介しました.
具体的には,動的リンク,静的リンク,動的ロードのコード一式を実行することで,これらの違いを確認しました.
C言語を独学で習得することは難しいです.
私にC言語の無料相談をしたいあなたは,公式LINE「ChishiroのC言語」の友だち追加をお願い致します.
私のキャパシティもあり,一定数に達したら終了しますので,今すぐ追加しましょう!
独学が難しいあなたは,元東大教員がおすすめするC言語を学べるオンラインプログラミングスクール5社で自分に合うスクールを見つけましょう.後悔はさせません!