C++20 module に関する調査
概要
目的
module 機能を使用することで成果物にどう影響を与えるのかについて重点的に調査したい。
コーディングや仕様については省略することがある。
コメントの順番がめちゃくちゃで申し訳ない
環境
$ g++ --version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ clang --version
Ubuntu clang version 14.0.0-1ubuntu1.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
clang++ を利用する際、'iostream' file not found
とエラーが表示され、コンパイルが失敗することがある。エラーを解消するだけであれば、 g++-12
を追加でインストールすることで解消できる。詳細は以下の記事を参照。
参考資料
翻訳単位の分類
モジュールユニットとグローバルモジュール(=モジュールでない従来のもの)に区別される。
モジュールユニットは以下のようにさらに細分化される。
モジュールインターフェースユニット | モジュール実装ユニット | |
---|---|---|
モジュール本体 | プライマリモジュールインターフェース | モジュール実装本体ユニット |
パーティション | モジュールインターフェースパーティション | モジュール実装パーティション |
モジュール宣言(翻訳単位の開始)は以下の通り
モジュールインターフェースユニット | モジュール実装ユニット | |
---|---|---|
モジュール本体 | export module A |
module A |
パーティション | export module A:z |
module A:z |
パーティションとは、モジュールを構成するファイルをさらに分割するためのもの。
『パーティションを使わない場合モジュールは1ファイルで書き切る必要があるということ?』
フラグメント
フラグメントは、モジュールとはまた独立した単位である。「プライベートモジュールフラグメント」と「グローバルモジュールフラグメント」がある。
『名前は似てるけど用途は全く別物っぽいね。』
プライベートモジュールフラグメント
https://cpprefjp.github.io/lang/cpp20/modules.html
プライベートモジュールフラグメントは、1ファイルでモジュールを定義しつつインターフェースと実装を分離するための機能である。
プライベートモジュールフラグメントを記述する場合、そのモジュールは翻訳単位(必然的にモジュールインターフェースユニット)を1つしか持つことができない。
『モジュールを真面目に実装しようとすると、複数の翻訳単位から構成する必要がある。プライベートモジュールフラグメントは、モジュールインターフェースユニットだけでモジュールを書き切りたいときに使う。っぽい。』
グローバルモジュールフラグメント
https://cpprefjp.github.io/lang/cpp20/modules.html
モジュール宣言の前にグローバルモジュールの実装を書ける。これをグローバルモジュールフラグメントという。
グローバルモジュールフラグメント内の宣言や定義は、後続のモジュールではなくグローバルモジュールに属する。
グローバルモジュールフラグメントにはプリプロセッサディレクティブ以外を書くことはできない。
includeヘッダを書くための区間。
『#include
が記述できて、プリプロセッサ以外の記述が出来ないとは?インクルード先のコードに制限は無い?』
リンケージ
外部から呼び出せるかどうか。
従来のC++(C)では主に外部リンケージと内部リンケージがある。
外部リンケージは、宣言さえあればその実装に参照できる。
内部リンケージは、宣言があっても実装は隠蔽され参照できない。(static や無名空間付けるやつ)
モジュールにおいて、
外部リンケージは、 export を付加して宣言したもの。
内部リンケージは、従来同様 static などを付与したもの。
export も static なども付与しないものは、「モジュールリンケージ」として扱われる。モジュールリンケージは、モジュール同士であればアクセス可能。
『Javaっぽい』
Hello world を書いてみよう…といったところで拡張子の問題がある
モジュールファイルの拡張子
コンパイラ・処理系によって思想が異なる。
https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Modules.html
No new source file suffixes are required. A few suffixes preferred for module interface units by other compilers (e.g. ‘.ixx’, ‘.cppm’) are supported, but files with these suffixes are treated the same as any other C++ source file.
https://clang.llvm.org/docs/StandardCPlusPlusModules.html
File name requirements
By convention, importable module unit files should use .cppm (or .ccm, .cxxm, or .c++m) as a file extension. Module implementation unit files should use .cpp (or .cc, .cxx, or .c++) as a file extension.
以降はモジュールインターフェースについては cppm を、それ以外は cpp を選択。
コーディングスタイル
普段 Google C++ Style Guide に従って書いているので、今回もこちらに従おうと思う。
https://google.github.io/styleguide/cppguide.html#modules
Do not use C++20 Modules.
そうですか…
Clang であれば、C++ module のサンプルコードとして信頼性がありそう。
module を clang でビルドする
プライマリモジュールインターフェース のみから成るモジュールと、それを使用するコードでプログラムを組んでみる。
module;
#include <vector>
#include <iostream>
export module Base; // プライマリモジュールインターフェース
namespace base {
export class Table {
public:
Table(int height, int width)
: height_(height), width_(width), data_(height * width) {}
int operator()(int y, int x) const { return data_[y * width_ + x]; }
int &operator()(int y, int x) { return data_[y * width_ + x]; }
void print() const {
for (std::size_t y = 0; y < height_; ++y) {
for (std::size_t x = 0; x < width_; ++x) {
std::cout << (*this)(y, x) << ' ';
}
std::cout << '\n';
}
}
private:
std::size_t height_, width_;
std::vector<int> data_;
};
} // namespace base
#include <iostream>
import Base;
int main() {
base::Table table(3, 3);
table(0, 0) = 1;
table(0, 2) = 3;
table(1, 1) = 1;
table(2, 2) = 1;
table.print();
return 0;
}
ビルドコマンドは以下の通り
(mkdir -p out && cd out \
&& clang++ -std=c++20 ../base.cppm --precompile -o base.pcm \
&& clang++ -std=c++20 -fmodule-file=Base=base.pcm ../main.cpp -o main)
実行すると以下の通り
$ ./out/main
0 1 3
0 0 1
2 0 0
-fmodule-file
の代わりに -fprebuilt-module-path
でディレクトリごと指定できる。
clangにおいては、このどちらかの方法でモジュールを指定することになる。
$ (mkdir -p out && cd out \
&& clang++ -std=c++20 ../base.cppm --precompile -o base.pcm \
&& clang++ -std=c++20 -fprebuilt-module-path=. ../main.cpp -o main)
../main.cpp:3:8: fatal error: module 'Base' not found
import Base;
~~~~~~~^~~~
1 error generated.
import Base;
と記述すると、Base.pcm
を探しにいくらしい。
$ cd out
$ mv base.pcm Base.pcm
$ clang++ -std=c++20 -fprebuilt-module-path=. ../main.cpp -o main
$ ./main
0 1 3
0 0 1
1 0 0
この挙動に従うならば、モジュール名とファイル名は同一のものにしたほうがよさそう。
ドキュメント見たら既に書いてあった。
https://clang.llvm.org/docs/StandardCPlusPlusModules.html#file-name-requirements
A BMI should use .pcm as a file extension. The file name of the BMI for a primary module interface unit should be module_name.pcm. The file name of a BMI for a module partition unit should be module_name-partition_name.pcm.
Base.pcm のような中間ファイルは Built Module Interface (BMI) と呼ばれる。
ファイルサイズを見てみる。
$ ls -g -G
total 6092
-rw-rw-r-- 1 6216528 9月 16 17:23 Base.pcm
-rwxrwxr-x 1 20472 9月 16 17:24 main
ちなみにモジュールとして記述しない場合、オブジェクトファイルのサイズは、
-rw-rw-r-- 1 1134948 9月 16 18:00 base.o
Base.pcm
自体がヘッダに関する情報を保有していると考えられるが
https://clang.llvm.org/docs/StandardCPlusPlusModules.html#reduced-bmi
Users can use the -fexperimental-modules-reduced-bmi option to produce a Reduced BMI.
手元の clang では使えないので省略。
two-phase compilation
というキーワードが出てくるのでこれについて調べる。
BMI の生成には --precompile
を指定する方法(従来の lib.o
を生成するのと同じ)と -fmodule-output
を指定する方法がある。--precompile
の方を two-phase compilation
、-fmodule-output
の方を one-phase compilation
と呼ぶ。
-fmodule-output
は手元の clang では使えないので省略。
https://clang.llvm.org/docs/StandardCPlusPlusModules.html#how-to-produce-a-bmi によると、オブジェクトファイルの生成までに2段階のビルドが必要であるため、two-phase compilation
と呼ばれているらしい。
『後で考える:BMI はあくまでも clang がビルド時に使用する中間ファイルであってオブジェクトファイルではない?C++ライブラリのリリースという観点では BMI の使用は不適切か?』
C++20 module は Precompiled Headers と深く関係しているようなので、Precompiled Headers についても調べる。
通常ソースファイルをビルドするときは、#include
でヘッダファイルをソースファイルに挿入したり、マクロを適用したりするなどプリプロセッサ命令を処理してからコンパイル等が行われる。1つのヘッダファイルが複数のソースファイルから何度もインクルードされていると、その分コンパイル等も多重に行われる。
Precompiled Headers は、よく使うヘッダファイルを予め解析して中間ファイルを生成しておくことで、以降そのヘッダファイルがインクルードされた時はその中間ファイルを代わりにロードし、ヘッダファイルの解析処理を省略する。この機能はビルド手順に関するもので、 C++ 標準で定められたものではない。はず。
ビルド手順等は Module に非常に似ている。Module は Precompiled Headers と同様にヘッダファイルの多重ロードを回避しビルド時間を短縮するために策定されたらしい。
Clang で Precompiled Headers
Clang では Clang’s precompiled headers (PCH)
と表現される。生成される中間ファイルの拡張子は .pch。
Clang で PCH を生成するには、-emit-pch
を使う。ただし、この引数を使うには内部向け引数である -cc1
が必要。 clang はデフォルトで -target-cpu
等のオプションをいい感じに指定するが、 -cc1
はそれを行わない。
うまく動かせなかったので略
-cc1
が必要と書いてあるが、なくても使えるらしい
GCC で Precompiled Headers
生成される中間ファイルの拡張子は .gch。
GCC で Precompiled Headers を生成するには、出力ファイルの拡張子を .gch
にするだけ。追加の引数は不要。このとき、生成ファイルは、ヘッダファイルと同じ場所かつヘッダファイル名前に .gch
をつけたものにする。例えば、ヘッダファイルが /tmp/work/lib.h
なら、出力先は /tmp/work/lib.h.gch
でなければならない。
gcc -o ../lib.h.gch -c ../lib.h
gcc -o lib.o -c ../lib.c
gcc -o main.o -c ../main.c
gcc lib.o main.o -o main
引数に -H を指定すると、どのヘッダをロードしたかが分かる。.gchも表示される。
$ # .gchがないとき
$ gcc -H -o main.o -c ../main.c
. ../lib.h
$ # .gchがあるとき
$ gcc -H -o main.o -c ../main.c
! ../lib.h.gch
../main.c
CMake で Precompiled Headers
CMake も Precompiled Headers に対応している。 target_precompile_headers
で生成できる。
cmake_minimum_required(VERSION 3.16)
# set(CMAKE_C_COMPILER "/usr/bin/clang" CACHE string "clang compiler" FORCE)
# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -H")
project(Precompile)
add_library(lib lib.c lib.h)
target_precompile_headers(lib PRIVATE lib.h)
add_executable(main main.c)
target_link_libraries(main PRIVATE lib)
target_precompile_headers(main REUSE_FROM lib) # libで生成したPCHを使う
ビルドシステムには ninja を使う。
$ mkdir -p out && cd out
$ cmake .. -GNinja
...
$ ninja all
[5/5] Linking C executable main
cmake_pch.h.gch が生成されているのが確認できる。cmake_pch.h にいい感じヘッダを固めてくれる。らしい。
$ tree CMakeFiles/lib.dir/
CMakeFiles/lib.dir/
├── cmake_pch.h
├── cmake_pch.h.c
├── cmake_pch.h.gch
└── lib.c.o
0 directories, 4 files
ちなみに、CMake かつ Clang で PCH を生成しようとした場合でも、中間ファイルの拡張子は gch になってた。
Precompiled Headers ここまで
ここからは Modules に戻る
-fprebuilt-module-path の仕様により、すべてのモジュールの中間ファイルは1つのディレクトリに配置することになる。ソースファイルの位置は関係なく、出力ディレクトリに配置すれば良いので、PCHよりは使いやすそう。
モジュール名には「識別子または識別子をドットで繋いだもの」が使える。モジュール名は base.math.Matrix
のような名前を意図している気がする。
module を gcc でビルドする
https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Modules.html
G++’s modules support is not complete.It is independent of the language version selected, although in pre-C++20 versions, it is of course an extension.
実装はまだ不完全らしい。-fmodules-ts
を付加する必要がある。C++20 以前のモードであっても -fmodules-ts
さえ付加すれば module を扱える。
ビルドコマンドは以下の通り。ソースコードは「module を clang でビルドする」と同じ。
mkdir -p out.gcc && cd out.gcc \
&& g++ -fmodules-ts -std=c++20 -x c++ ../base.cppm -c -o base.o \
&& g++ -fmodules-ts -std=c++20 -O0 -g ../main.cpp -c -o main.o \
&& g++ base.o main.o -o main
.cppm
が C++ として認識されないので -x c++
で教える。base.o
の通り、GCC の場合はオブジェクトファイルも出力する。そしてこれもリンクしないと、ビルドに失敗することを確認した。
生成物ディレクトリは以下のようになる。base.gcm が生成されている。
$ tree out.gcc/
out.gcc/
├── base.o
├── gcm.cache
│ └── base.gcm
├── main
└── main.o
1 directory, 4 files
「module を clang でビルドする」で示したソースコードでは、main.cpp
では使用していない iostream
をインクルードしている。
#include <iostream> // これ
import Base;
この行をコメントアウトすると、clang++ではビルドは成功するが、実行時にセグメンテーションフォルトが発生する。GCCは取り除いても良い。
ChatGPT(gpt-4) に投げてみたが、「自己完結型であるという考え方」に関する出典が分からず
C++のモジュールシステムは、それぞれのモジュールが自己完結型であるという考え方に基づいています。つまり、モジュールは必要な全てのリソースを自身で提供するべきで、これには使用するヘッダーファイルも含まれます。
したがって、<iostream>
のようなヘッダーファイルがモジュール内で使用されている場合、そのモジュールをインポートする全てのコード(この場合、main.cpp
)では、それらのヘッダーファイルも直接または間接的に利用可能である必要があります。
その一方で、現在のClang++の実装では、モジュールでインクルードしたヘッダーファイルが自動的にインポート側にエクスポートされないためこの問題が起きています。つまり、モジュール内で使用したインクルードが、そのモジュールを利用するソースコードに対して自動的に適用されないという現象が発生します。その結果、モジュール内で<iostream>
が必要な場合、main.cpp
でもそれをインクルードする必要が発生します。
この問題を回避するためには、main.cpp
内で必要なヘッダーファイルを直接インクルードするか、base.cppm
内でexport import
を用いてインポートされたヘッダーファイルをエクスポートする必要があります。
https://learn.microsoft.com/en-us/cpp/cpp/modules-cpp?view=msvc-170#imported-header-files で self-contained
は使われているけど、これはヘッダーに関する記述(ヘッダー単独でコンパイルができるという意味?)。
インターフェースと実装を分けたコード
プライマリモジュールインターフェースに実装を書いていたが、これを分けてみる。
こちらには、定義のみを書く。
module;
#include <vector>
export module base;
namespace base {
export class Table {
public:
Table(int height, int width);
int operator()(int y, int x) const;
int &operator()(int y, int x);
void print() const;
private:
std::size_t height_, width_;
std::vector<int> data_;
};
} // namespace base
モジュール実装本体ユニットを別のファイルに書く。
module;
#include <iostream>
#include <vector>
module base;
namespace base {
Table::Table(int height, int width)
: data_(height * width), height_(height), width_(width) {}
int Table::operator()(int y, int x) const {
return data_[y * width_ + x];
}
int &Table::operator()(int y, int x) {
return data_[y * width_ + x];
}
void Table::print() const {
for (std::size_t y = 0; y < height_; ++y) {
for (std::size_t x = 0; x < width_; ++x) {
std::cout << (*this)(y, x) << ' ';
}
std::cout << '\n';
}
}
} // namespace base
GCC によるビルドは以下の通り。全部ソースファイルとしてビルドする。
mkdir -p out.gcc && cd out.gcc
g++ -fmodules-ts -std=c++20 -Os -x c++ ../base.cppm -c -o base.cppm.o
g++ -fmodules-ts -std=c++20 -Os -x c++ ../base.cpp -c -o base.o
g++ -fmodules-ts -std=c++20 -Os ../main.cpp -c -o main.o
g++ base.cppm.o base.o main.o -o main
Clang は…ビルド通らない…
mkdir -p out.clang && cd out.clang
clang++ -std=c++20 ../base.cppm --precompile -o base.pcm
clang++ -std=c++20 -Os -fprebuilt-module-path=. ../base.cpp -c -o base.o
clang++ -std=c++20 -Os -fprebuilt-module-path=. ../main.cpp -c -o main.o
clang++ base.o main.o -o main
../base.cpp:6:1: error: definition of module 'base' is not available; use -fmodule-file= to specify path to precompiled module interface
module base;
^
module を clang でビルドする(2)
https://clang.llvm.org/docs/StandardCPlusPlusModules.html#quick-start
Clang の quick-start を改めて読んだら、 pcm
ファイルもオブジェクトにコンパイルしているようだった。clang++ -std=c++20 ./base.pcm -c -o base.module.o
のように、ソースファイルと同じように扱える。
リンク時にpcmの情報が入るのでセグメンテーションフォルトが改善されるかと期待したが、エラーは消えない。
clang++ -fmodules-ts -Wall -std=c++20 -Os ../base.cppm --precompile -o base.pcm
clang++ -fmodules-ts -Wall -std=c++20 -Os ./base.pcm -c -o base.module.o
clang++ -fmodules-ts -Wall -std=c++20 -Os -fprebuilt-module-path=. ../main.cpp -c -o main.o
clang++ base.module.o main.o -o main
module を clang でビルドする(3)
標準ライブラリとして、libc++
(llvm) を使うよう指示すれば、セグメンテーションフォルトなく起動できることが分かった。デフォルトでは libstdc++
(gcc) が使用されるが、こちらは失敗する。
clang++ -std=c++20 -stdlib=libc++ ../hello.cppm --precompile -o hello.pcm
clang++ -std=c++20 -fprebuilt-module-path=. ./hello.pcm -c -o hello.o
clang++ -std=c++20 -stdlib=libc++ -fprebuilt-module-path=. ../main.cpp -c -o main.o
clang++ -stdlib=libc++ hello.o main.o -o main
module を clang でビルドすると SIGSEGV が発生する問題の調査
iostream に依存する最小構成のモジュールを用意する。
module;
#include <iostream>
export module hello;
export void greedy() {
std::cout << "Hello, Modules!" << std::endl;
}
import hello;
int main() {
greedy();
return 0;
}
#include <iostream>
import hello;
int main() {
greedy();
return 0;
}
ビルドは以下の通り。main
は実行に失敗し、main2
は実行に成功するバイナリ。
mkdir -p out.clang.std && cd out.clang.std
clang++ -std=c++20 -v -stdlib=libstdc++ ../hello.cppm --precompile -o hello.pcm
clang++ -std=c++20 -fprebuilt-module-path=. ./hello.pcm -c -o hello.o
clang++ -std=c++20 -stdlib=libstdc++ -fprebuilt-module-path=. ../main.cpp -c -o main.o
clang++ -stdlib=libstdc++ hello.o main.o -o main
clang++ -std=c++20 -stdlib=libstdc++ -fprebuilt-module-path=. ../main2.cpp -c -o main2.o
clang++ -stdlib=libstdc++ hello.o main2.o -o main2
cd -
objdump
でバイナリを比較してみる。
objdump -d -l main > main.dump.txt
objdump -d -l main2 > main2.dump.txt
main2 には以下のような __cxx_global_var_init
の定義と呼び出しがあるが、main には含まれていない。
0000000000001070 <__cxx_global_var_init>:
__cxx_global_var_init():
...略...
00000000000010a0 <_GLOBAL__sub_I_main2.cpp>:
_GLOBAL__sub_I_main2.cpp():
10a0: 55 push %rbp
10a1: 48 89 e5 mov %rsp,%rbp
10a4: e8 c7 ff ff ff call 1070 <__cxx_global_var_init>
10a9: 5d pop %rbp
10aa: c3 ret
10ab: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
__cxx_global_var_init
とは、C++ でグローバル変数のコンストラクタを呼び出したりするための実装。
iostream のグローバル変数の初期化呼び出しが消えてしまったのが原因で、iostream が初期化されておらず、 std::cout
の中で SIGSEGV が発生してしまったのでは。
ちなみに、SIGSEGV の発生箇所は lldb
で確認できる。
* thread #1, name = 'main', stop reason = signal SIGSEGV: invalid address (fault address: 0xffffffffffffffe8)
frame #0: 0x00007ffff7d3bf8a libstdc++.so.6`std::ostream::sentry::sentry(std::ostream&) + 26
libstdc++.so.6`std::ostream::sentry::sentry:
-> 0x7ffff7d3bf8a <+26>: movq -0x18(%rax), %rdi
0x7ffff7d3bf8e <+30>: addq %rsi, %rdi
0x7ffff7d3bf91 <+33>: movq 0xd8(%rdi), %rax
0x7ffff7d3bf98 <+40>: movl 0x20(%rdi), %esi
(lldb) bt
* thread #1, name = 'main', stop reason = signal SIGSEGV: invalid address (fault address: 0xffffffffffffffe8)
* frame #0: 0x00007ffff7d3bf8a libstdc++.so.6`std::ostream::sentry::sentry(std::ostream&) + 26
frame #1: 0x00007ffff7d3ca0c libstdc++.so.6`std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) + 60
frame #2: 0x00007ffff7d3cebb libstdc++.so.6`std::basic_ostream<char, std::char_traits<char> >& std::operator<<<std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) + 43
frame #3: 0x0000555555555157 main`greedy() + 23
frame #4: 0x0000555555555174 main`main + 20
frame #5: 0x00007ffff7829d90 libc.so.6`__libc_start_call_main(main=(main`main), argc=1, argv=0x00007fffffffdb28) at libc_start_call_main.h:58:16
frame #6: 0x00007ffff7829e40 libc.so.6`__libc_start_main_impl(main=(main`main), argc=1, argv=0x00007fffffffdb28, init=0x00007ffff7ffd040, fini=<unavailable>, rtld_fini=<unavailable>, stack_end=0x00007fffffffdb18) at libc-start.c:392:3
frame #7: 0x0000555555555075 main`_start + 37
module を clang でビルドすると SIGSEGV が発生する問題の調査(2)
-stdlib=libc++
を付与してビルドすれば SIGSEGV は発生しないが、こちらのバイナリを objdump -d -l
で確認してみると、__cxx_global_var_init
は含まれていなかった。libc++
に含まれる iostream
がグローバル変数のクラスコンストラクタを使っていないだけかも。
そもそも clang++ -std=c++20 -fprebuilt-module-path=. ../hello.cppm -c -o hello.o
で生成される hello.o
に __cxx_global_var_init
が含まれていない。module でない従来の実装方法であれば含まれるはず。
結局、想定解は分からず…
生成される .pcm (中間ファイル) は絶対パスを含む
生成される .pcm は絶対パスを保持しているので、ディレクトリを移動した場合は、たぶん再ビルドが必要。
また、中間ファイルに対応する .cppm を削除した状態でそのモジュールを使用するソースファイルをコンパイルすると、エラーが発生する。
../main.cpp:1:1: fatal error: malformed or corrupted AST file: 'could not find file '/tmp/work/out.clang.std/../hello.cppm' referenced by AST file '/tmp/work/out.clang.std/./hello.pcm''
import hello;
^
1 error generated.
まとめ
- モジュールは、ビルドの高速化を図る Precompiled Headers を標準っぽくしたもの。
- モジュールのインターフェースは Built Module Interface (BMI) とか Compiled Module Interface (CMI) とか呼ばれる中間ファイルに変換される。この中間ファイルはキャッシュの一種で、オブジェクトファイルではない。
- モジュールを利用する手順(普通この手順は CMake 等で隠蔽できる)
- モジュールインターフェースから中間ファイルを作成する。
- モジュール実装をコンパイルしてオブジェクトファイルを作る。
- モジュールを使用するソースファイルをコンパイルしてオブジェクトファイルを作る。
- オブジェクトファイルをリンクする。
- (使用したコンパイラが古いだけかもしれないが)まだ発展途上で、挙動がコンパイラに強く依存している。
MSVC も確かめたほうがいいかもしれない
module を clang でビルドすると SIGSEGV が発生する問題の調査
Clang 19 をダウンロードしてきた
$ clang++ --version
clang version 19.1.0 (/home/runner/work/llvm-project/llvm-project/clang a4bf6cd7cfb1a1421ba92bca9d017b49936c55e4)
Target: x86_64-unknown-linux-gnu
Thread model: posix
InstalledDir: /home/mai/Downloads/LLVM-19.1.0-Linux-X64/bin
$ ./out.clang.std/main
Hello, Modules!
libstdc++でも普通に動くじゃん!コンパイラのバグじゃん!