はじめに
こんにちは、@kakira9618 です。
この記事は CCS †裏† Advent Calendar 2018 の25日目(最終日)の記事です。
前日の記事は 抱き枕で幸福度の高い生活を です。
本当は最後の日ということでまとめっぽいのを書きたかったのですが、都合の関係上環境設定記事となりました。許して。。。
内容は、競技プログラミング(競プロ)向けのエディタの設定その他の話です。
PC
主に、以下のPCで競技プログラミングを行っています。
- ハードウェア: MacBook Pro (15-inch, Late 2016)
- CPU: 2.7 GHz Intel Core i7
- MEM: 16 GB 2133 MHz LPDDR3
- SSD: 512GB SSD
- OS: MacOS Mojave 10.14.2
この中で、特に重要なのはOSですね。特に macOS は OSアップデートするたびにCommandLine Toolのインストールのやり直しが必要なので、注意が必要です。
PCの性能自体は、競プロにおいてはそこまで必要になることは多くないです。Core i3, メモリ4GB くらいのPCがあれば十分だと思います。
エディタ
私は競プロにおいてはSublime Text 2を使っています。
Sublime Text 2の選定理由
Sublime Textを選んだ理由は以下のとおりです。
- 軽い(Webベースのエディタ(Atom, VS Code など)と比べて)
- ビルドなどの設定を比較的簡単に行える
- 一般的に、コマンドラインが合体したUIのエディタが多くありますが、実行のたびに入力をコピーペーストで渡すのが少々面倒なため、私は、テキストファイルに入力を書いておき、cat + パイプによりファイル内容を標準入力に渡す、という手法を取っています。この設定がSublime Textでは比較的簡単に行えます。(他でも簡単に行えるかもしれません。未検証)
- コードスニペットが使える
- 自由にコードの断片を登録して、それを呼び出せる機能です。これは現代的なエディタならほとんどそうですね。競プロをする際は、典型的な処理(forなど)は間違いなくすばやく書きたくなるので、スニペットを使うことが多いです
- エディタのコマンドが比較的簡単に作れる
- シェルスクリプトを呼び出したりできるので、活用の幅は無限大です(?)
- マルチカーソルが便利
- カーソルを複数置いて、同時に編集できる機能です。
Sublime Textはシェアウェアなのですが、無料でも無期限に機能制限なく使うことができます。ただし、保存時に一定確率(20回に1回くらい?)でシリアルキーを買わないか?というダイアログが出ます。私はSublime Text 2をよく使うので、シリアルキーを購入しています。
ところで、Sublime Text 2 はもう開発が終了しているエディタ1です。なぜわざわざ2を使っているかというと、設定移行のコストが高そうに見えるから、及び、デザインが好みだから、です。そんなに大きな理由ではないので、今後移行することも考えられます。
Sublime Text の設定
Sublime Text のインストール
インストールは 公式サイト から自分のOSに合ったものを選んで行ってください。この記事はSublime Text 2での設定ですが、3でも大きく変わることは無いはずです(適宜読み替えてください)。
ビルドの設定
Tools->Build System->New Build Systemで新たなビルドの設定ファイルを作成することができます。この設定ファイルの中で、ビルドコマンドを実行したときに実際に行うコマンドなどを設定していきます。
私は次のように設定しています。設定が終わったら名前をつけて保存します。ファイル名がビルド設定名になります。
{
"cmd": [
"/usr/local/bin/g++-7",
"${file}",
"-o", "${file_path}/test",
"-std=gnu++17",
"-g",
"-D_GLIBCXX_DEBUG", // vector<T> などの領域外参照検出
"-fsanitize=undefined", // 一般的なUB検出
"-fvisibility=hidden", // ld warning抑止用
"-fvisibility-inlines-hidden" // ld warning抑止
],
"file_regex": "^(..[^:]*):([0-9]+):?([0-9]+)?:? (.*)$",
"working_dir": "${file_path}",
"selector": "source.cpp",
"variants":
[
{
"name": "Run",
"cmd": ["touch '$file_path/input.txt' ; cat '$file_path/input.txt' | '$file_path/test'"],
"shell" :true
},
{
"name": "Debug",
"cmd": ["lldb -s debug test"],
"shell" :true
}
]
}
"Cmd" となっている部分に Command + Shift + b
を押したときに行いたいコマンドを定義します。
同じく "Run" に Command + b
を押したときに行いたいコマンドを定義します。
"debug"というのは私が勝手に追加したもので、これはCommand + Shift + p
を押して、該当の名前(今回は"debug")の一部を入力して決定すると使えるコマンドです。
各操作について、次のような設定を行っています。
-
Command + Shift + b
を押した時- プログラムをコンパイルして、同ディレクトリに
test
という名前の実行ファイルを作成する。デバッグ情報(-g
オプションなど)もつけておくことで、lldbでのデバッグがやりやすくなります。
- プログラムをコンパイルして、同ディレクトリに
-
Command + b
を押した時- 同ディレクトリにある
test
という名前の実行ファイルを実行する。このとき、同ディレクトリにあるinput.txt
というファイルの中身をリダイレクトしてtestにわたす
- 同ディレクトリにある
-
Command + Shift + p
→ "debug"- デバッグコマンドを実行する(後述)
コンパイラは macOS 標準のもの(clang)ではなく、g++7をhomebrewでインストールして使っています。パスは特に設定せず(設定すると他の場所でエラーが起きがちなので……)、コンパイラの絶対パスを指定しています。なぜ標準のものではなく、g++を使っているかというと
-
#include <bits/stdc++.h>
ができる - 一部のコマンドオプション(
-D_GLIBCXX_DEBUG
,-fsanitize=undefined
)がうまく動く
からです。前者は、C++でよく使うヘッダなどをまとめてincludeしているヘッダで、g++で使えます。後者は実行時にヤバイ挙動をしたときに教えてくれるオプションです(-D_GLIBCXX_DEBUGはSTLでの領域外参照などを教えてくれます。-fsanitize=undefinedは領域外参照やオーバーフローなどが起きたら教えてくれます)。clangにも同様のオプション(名前は違う)が存在するみたいですが、なかなかうまく動かなかったので。
プログラムを書いて実行
適当なディレクトリを作って、その中にtest.cpp
、input.txt
を作ります。
Sublime Textを左右分割(View->Layout->Columns: 2)して、test.cpp
を左側、input.txt
を右側で開きます。(ドラッグアンドドロップが簡単です)
適当なプログラムと入力を書きましょう。さしあたり
#include <bits/stdc++.h>
int main() {
int N; std::cin >> N;
std::cout << N << std::endl;
return 0;
}
1053
こんな感じで良いでしょう。保存(Command + s
)したあと、コンパイル(Command + Shift + b
)して実行(Command + b
)します。
1053
[Finished in 0.1s]
などと出てきたらOKです。
デバッグコマンド
ビルドオプションに、 -D_GLIBCXX_DEBUG
などを指定すると、std::vectorなどで領域外参照したときにそのことを教えてくれます。
#include <bits/stdc++.h>
int main() {
std::vector<int> A(2);
A[2] = 1; // 領域外参照
return 0;
}
/usr/local/Cellar/gcc@7/7.3.0/include/c++/7.3.0/debug/vector:417:
Error: attempt to subscript container with out-of-bounds index 2, but
container only holds 2 elements.
Objects involved in the operation:
sequence "this" @ 0x0x7ffeec9f2b00 {
type = std::__debug::vector<int, std::allocator<int> >;
}
/bin/sh: line 1: 7006 Done cat '/Users/kakira/Desktop/testcpp/input.txt'
7007 Abort trap: 6 | '/Users/kakira/Desktop/testcpp/test'
[Finished in 0.1s with exit code 134]
これを読むと2要素のコンテナの2番目の要素(0-originなので実質3番目)をアクセスしようとしてエラーが起きたことがわかりますが、どこでそれが起きたかがわかりません。
これを表示させるのにはlldb(デバッガ)が必要ですが、そのためにターミナルを立ち上げるのは面倒です。ここで、カスタムコマンド("debug"コマンド)の登場というわけです。
input.txt
などが置いてあるディレクトリにdebug
というテキストファイルを作り、以下を記入します。
process launch -i input.txt
bt
q
y
これはlldb内で行ってほしいことを羅列したものになっています。標準入力をinput.txt
にすることや、エラー落ちしたらbtをしてバックトレースを表示することなどが記述されています。
実際、先程のプログラムで、Command + Shift + p
→ debug
と入力、Enter とすると、
(lldb) target create "test"
Current executable set to 'test' (x86_64).
(lldb) command source -s 0 'debug'
Executing commands in '/Users/kakira/Desktop/testcpp/debug'.
(lldb) process launch -i input.txt
/usr/local/Cellar/gcc@7/7.3.0/include/c++/7.3.0/debug/vector:417:
Error: attempt to subscript container with out-of-bounds index 2, but
container only holds 2 elements.
Objects involved in the operation:
sequence "this" @ 0x0x7ffeefbffa30 {
type = std::__debug::vector<int, std::allocator<int> >;
}
Process 7034 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
frame #0: 0x00007fff5920223e libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
-> 0x7fff5920223e <+10>: jae 0x7fff59202248 ; <+20>
0x7fff59202240 <+12>: movq %rax, %rdi
0x7fff59202243 <+15>: jmp 0x7fff591fc3b7 ; cerror_nocancel
0x7fff59202248 <+20>: retq
Target 0: (test) stopped.
Process 7034 launched: '/Users/kakira/Desktop/testcpp/test' (x86_64)
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
* frame #0: 0x00007fff5920223e libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007fff592b8c1c libsystem_pthread.dylib`pthread_kill + 285
frame #2: 0x00007fff5916b1c9 libsystem_c.dylib`abort + 127
frame #3: 0x0000000100145452 libstdc++.6.dylib`__gnu_debug::_Error_formatter::_M_error() const + 626
frame #4: 0x0000000100001736 test`std::__debug::vector<int, std::allocator<int> >::operator[](this=0x00007ffeefbffa30, __n=2) at vector:417
frame #5: 0x000000010000118f test`main at test.cpp:5
frame #6: 0x00007fff590c2ed9 libdyld.dylib`start + 1
(lldb) q
[Finished in 1.0s]
などと出て、frame #5: でtest.cppの5行目でエラーが起きたことが分かります。(コツはat vector:417と書いてある1行下のフレームを見ることです。)
このコマンドを実行するのは2秒位あればできるので、脳死で(?) Runtime Error が起きたところの場所を特定できるようになります。結構気に入っています。
こんな感じに動きます。
Sublime Textで、vectorの配列外参照を警告する設定&警告された行を探すためのコマンド一式を一発で行うコマンドを作った pic.twitter.com/CVGpnzDWXe
— kakiraちゃん@サンタさん (@kakira9618) 2018年8月18日
コンパイル時間の短縮
#include <bits/stdc++.h>
をすることにより、いろいろなヘッダのincludeを書かなくて良くなったのは良いのですが、コンパイル時間は増えます。
コンパイル時間を早くする手っ取り早い方法は、プリコンパイル済みヘッダーを作ることです。
ヘッダーの部分を予めコンパイルしてバイナリにしておくことにより、その部分のコンパイル時間が短縮されます。(ヘッダーが更新されると再コンパイルが必要になるので、あまり変更されないヘッダーがおすすめです)
やり方は簡単で、プリコンパイルしたいヘッダーを指定してg++を使ってコンパイルするだけです。ただし、コンパイルオプションは、メインプログラムのコンパイルオプションと揃えておきましょう。
$ cd /usr/local/Cellar/gcc@7/7.3.0/include/c++/7.3.0/x86_64-apple-darwin18.0.0/bits
$ /usr/local/bin/g++-7 stdc++.h -std=gnu++17 -g -D_GLIBCXX_DEBUG -fsanitize=undefined -fvisibility=hidden -fvisibility-inlines-hidden
これにより、1.6秒ほどかかっていた何もしないtest.cppファイルのコンパイルが、0.4秒ほどになりました。サクサクコンパイルできます。
スニペットの登録
最後に、よく使うテンプレをエディタに登録しましょう。試しに、競プロ用のヘッダ2を登録してみます。
Tools -> New Snnipetを開き、以下の内容を記述し、保存します(スニペットのCDATA内部は自由です)。
<snippet>
<description>Header for competive programming</description>
<content><![CDATA[#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
constexpr int dx[] = {1, 0, -1, 0, 1, 1, -1, -1};
constexpr int dy[] = {0, -1, 0, 1, 1, -1, -1, 1};
template <typename T> ostream &operator<<(ostream &os, const vector<T> &vec){os << "["; for (const auto &v : vec) {os << v << ","; } os << "]"; return os; }
template <typename T, typename U> ostream &operator<<(ostream &os, const pair<T, U> &p) {os << "(" << p.first << ", " << p.second << ")"; return os;}
void solve() {
${0}
}
int main() {
std::cin.tie(0);
std::ios::sync_with_stdio(false);
cout.setf(ios::fixed);
cout.precision(16);
solve();
return 0;
}
]]></content>
<tabTrigger>header</tabTrigger>
<scope>source.c, source.objc, source.c++, source.objc++</scope>
</snippet>
[CDATA[
の内部に貼り付けたい内容を書いていきます。tabTriggerにそれを呼び出すための名前を定義します。scopeは、これらの拡張子がついているものに対してこのスニペットを有効にする、というものです。
${0}
などはカーソルが置かれる位置です。
${0:/* code */}
などとすると、デフォルトの文字列を指定することができます。
さて、定義したスニペットを使ってみましょう。
test.cppに戻り、hea
(tabTriggerで設定した文字列の一部)くらいまで打ってTabキーを押します。
これでスニペットの貼付けが完了します。
もう一つスニペットを紹介します。forです。これはデフォルトの030-for-int-loop-(fori).sublime-snippet
を改造したものです(Sublime Text 2 -> Preferences -> Browse Package -> C++)。
<snippet>
<description>For Loop</description>
<content><![CDATA[for (int ${1:i} = 0; $1 < ${2:N}; $1++) {
${0:/* code */}
}]]></content>
<tabTrigger>for</tabTrigger>
<scope>source.c, source.objc, source.c++, source.objc++</scope>
</snippet>
複数カーソルの移動場所を定義するときは、${数字}
の数字の部分をインクリメントしていきます。Tabキーを押すと、数字の昇順にカーソルが移動していきます。${0}
は終着点です。(${3}
ではなく${0}
にしておくと、即座に次のスニペットを挿入しても大丈夫なのでおすすめです)
forのスニペット動作例:
これはSublime Textです。(ネイティブにコンパイルされていて、いろいろ軽量なのと、カスタマイズが簡単にできるところが気に入っています。)普段は動画のようにコードを左に、入力を右に、実行結果を下にしてコーディングしています。 pic.twitter.com/c6KT7Fd0tO
— kakiraちゃん@サンタさん (@kakira9618) 2018年10月6日
おわりに
いかがでしたか? これでCCS †裏† Advent Calendar 2018は終わりです。
良い競プロライフを!