【42Tokyo】Piscine受験5日前の日記
前置き
Piscine受験の5日前なのに、今日から北海道へスキー旅行に行くことになりました。
この記事を読んでいる人は恐らく、「一体何をやっているんだ?」と思うでしょう。
これについては、僕が一番そう感じている自信があります。
今、僕は大学受験の対策も始めているため、時間的な余裕はあまりないです。
加えて、家に帰ってくるのは恐らく受験当日です。
一応、前日の予定ではあるのですが、今の予定だと高確率で日付を超えたあたりで家に着くはずです。
初日から睡眠時間をあまり取れない可能性があるので、この観点からは厳しいスタートを迎えそうです…。
明日はあまりコーディングする時間がないと思うので、明日はその日起きたこと(日記)と、Piscineの戦略について書きたいと思います。
1/1 Piscine 5日前
ここ数日はPiscineの事前対策をできない日が続いていましたが、今日は割と多くの収穫があります。
ただ、今日はいつも取り組んでいるCLIエディタの制作はしていません。
これについて、今日はずっと移動時間だったりでシンプルに時間がなかったからという言い訳もできるのですが、今はともかく、C言語で書いたコードがあるので、これを解説していきたいと思います。
今日はC言語でstring(文字列型)を実装しました。
具体的には、次のようなC言語の文字ポインタ(char*)の致命的な弱点を克服し、C言語で実用的なコード(僕は一体何を言ってるんだろう)を書くための土台にしてしまおうという話です。
CLIエディタをコンポーネント指向に則った設計でコンストラクタ(に相当する関数)を作りまくったところ、設計のための設計が楽しくなってしまい、どうせなら文字列型まで実装してしまおうという話になりました。
文字ポインタのどこが終わってるのか?(自身の経験から)
・char配列は日本語のようなマルチバイト文字を扱いづらい
初心者がcharで日本語を文字列の処理関連で扱おうとすることは地獄の門を開けることと同義です。
初心者がcharで日本語を扱うことは前科よりも重いです。
まずはwchar_tを使いましょう。
・備え付きの関数があまりにも貧弱(そもそも文字ポインタだからそうなる)
string.hの関数は色々な面で弱いです。
本当にただの文字配列としての情報しか保管していないので、文字列の処理が長くなってしまいがちです。
・gccのデフォルトの関数だとメモリの境界チェックが行われず、大規模なプロジェクトだと120%実行中に事故を起こす
実行中にメモリ関連のエラーが発生すると、どこでエラーが発生したのか全く表示されない場合があるため、何がおかしいのかのデバッグに時間をかけてしまいます。
これは領域外に書き込んでも(デフォルトの状態では)何のエラーも出さないので、新しい関数を作る時間を考えても、圧倒的に作業時間を減らすことができます(知らんけど)。
もっとも、C言語の文字ポインタに関しては、本当に簡単な処理しか行われていないので、処理負荷という観点では他に出せる言語はそう多くないでしょう。
流石に今の時代だと組み込みでもC言語を使う事例が減ってきているほどなので、ここは事故の起きにくい設計のものに代替するか、他の言語を使ったりするのが良いでしょう。
(やっぱ42 Tokyoやってることえげつない)
閑話休題。
先述したような、他の言語と比較した時に発生する致命的な欠陥を克服するために、次のようなstring(構造体)を定義しました。
今回作成したstringの特徴
・C++のstringの仕様を参考に、文字列のデータ、使用可能な最大文字数、現在の文字の長さの三つの要素を持つ構造体を定義
他プログラミング言語のstringの仕様を調べてみたところ、C++のstringの仕様が向いていることに気づきました(当然)。
メモリ関連の問題を非常に回避しやすくなる(想定)ので、これを再現することにしました。
(もっとも私はC++をほんの少ししか書いたことがない)
・オブジェクト指向の構造体の感覚で使うことが可能
コンストラクタnew_string(…)の一行で構造体の全てを初期化できる。
仮でstringが必要な時(関数の引数にstringを要求された時)に一行でシンプルに記述することができると、可読性が上がって非常に便利です。
・文字列の初期化(new_string(…))をする時に、引数を省略することができる
本来C言語には存在しない仕様ですが、可変引数マクロを使用することで達成しました。
これはまだよく理解していないので、細部まで説明し切る自信がないです(CharGPTに作ってもらった)。
詳しくは実装の方を見てほしいのですが、引数が0だと空文字で初期化、引数が1だと与えられたワイド文字列で初期化、引数が2だと1の時に加えてバッファサイズを指定可能(これ実は境界チェックしてない)です。
また、コピーをするstring_copy(string *tmp1, string *tmp2, …)についても、引数が3(最低より一個多い)つだったら、その引数を領域が足りない時にreallocするかどうかのフラグにしたいです(まだ作ってない)。
具体例を含めた実装があるので、実際に書いたコードをお見せしたいと思います。
#include <locale.h>
#include <stdlib.h>
#include <wchar.h>
#define _count(...) _count_impl(__VA_ARGS__, 2, 1, 0)
#define _count_impl(_1, _2, N, ...) N
#define new_string(...) _new_string_counter(_count(__VA_ARGS__), __VA_ARGS__)
#define _new_string_counter(N, ...) _new_string_impl(N, __VA_ARGS__)
#define _new_string_impl(N, ...) new_string_##N(__VA_ARGS__)
typedef struct
{
wchar_t *string;
size_t capacity;
size_t length;
} string;
string new_string_0(void);
string new_string_1(wchar_t *tmp);
string new_string_2(wchar_t *tmp, size_t buffer_size_count);
size_t string_copy(string *tmp1, string *tmp2);
int main(void)
{
setlocale(LC_ALL, "");
string hoge = new_string(L"42");
string fuga = new_string(L"aaaaaaaa");
string_copy(&fuga, &hoge);
wprintf(fuga.string);
return 0;
}
string new_string_0(void)
{
string tmp_str;
tmp_str.length = 0;
tmp_str.capacity = 0;
tmp_str.string = malloc(sizeof (wchar_t));
tmp_str.string = wcscpy(tmp_str.string, L"");
return tmp_str;
}
string new_string_1(wchar_t *tmp)
{
string tmp_str;
tmp_str.length = wcslen(tmp);
tmp_str.capacity = tmp_str.length;
tmp_str.string = malloc((tmp_str.length + 1) * sizeof (wchar_t));
tmp_str.string = wcscpy(tmp_str.string, tmp);
return tmp_str;
}
string new_string_2(wchar_t *tmp, size_t buffer_size_count)
{
string tmp_str;
tmp_str.length = wcslen(tmp);
tmp_str.capacity = tmp_str.length;
tmp_str.string = malloc((buffer_size_count + 1) * sizeof (wchar_t));
tmp_str.string = wcscpy(tmp_str.string, tmp);
return tmp_str;
}
// 可能であれば自動reallocするバージョンも作って良い(なんなら3つめのオプションをそのフラグにしても良い)
size_t string_copy(string *tmp1, string *tmp2)
{
int i;
if (tmp2->capacity <= tmp1->capacity)
{
for (i = 0; i <= tmp2->length; i++)
{
tmp1->string[i] = tmp2->string[i];
}
}
else
{
fwprintf(stderr, L"確保した領域を超えて書き込もうとしています。");
exit(1);
}
return i;
}
このコード、実は北海道へ向かう飛行機や車の中で書いてます(入学前からエクストリームコーディングしてるタイプの人)。
今日はパイロットとしてではなく、普通に旅客としてですね。
(僕が操縦しているモーターグライダーで丘珠空港まで飛ぶ人がいるという恐怖)
ただ、PCを開いて作業をするのはいろいろと大変なので、飛行機の中ではiPhoneでa-shellというターミナルエミュレーターを使って作業をしました。
これは主要でないコマンドが使えなかったりはするものの、vimでコードを書いてpythonやclangで実行/コンパイルすることは問題なくできます。
ある程度までコードを書いた後にPCにAirDropで写した形になりますね。
Piscineの受験前から割と使えるC言語の要素を(再?)実装をしている人は恐らくかなり珍しいはずなので、これは本番に役立つものではないかと期待しています。
シンプルな日記エリア
今日は2025年最初の日だ。
昨日は睡眠時間を全く取れていなかったので、家に帰ったらすぐに寝てしまった。
このおかげで、今日は午前8時に起きることができた。。
ただ、睡眠時間は十分なはずなのになぜか寝足りないような気がしてしまい、結局10時ごろまで二度寝した。
体調が悪かったので…仕方ないよね…?
10時ごろに家族に起こされて、朝(昼?)食をいただいた。
今日はおせちだったのだが、僕はというとその料理の隣にあった大量のいくらを全部一人で食べていた。
おせちはほとんど食べていない。
最初は食べる量を4割に留めていたのだが、思ったよりも家族が食べなかったので、結局全て僕が食べることになった。
お年玉は母方のおばあちゃんと母親から受け取った。
母方のおばあちゃんは近畿地方にいてあまり会う機会がないのだが、その年齢にしては非常に元気らしい。
額は確認していないが、多分両方とも1万円だと思う。
予定していた午後一時から少し遅れた頃に家にタクシーが配車され、父親と合流するため本宅へと向かった。
今まで全く言及していなかったのだが、つまり、今住んでいる家は2つ目の住居ということになる。
本宅では、父と父型のおばあちゃんから直接、お年玉をもらった。
父からは2万円で、父型のおばあちゃんからは1万円だった。
この時、父型のおばあちゃんに白髪の割合が数割というほどに増えていたのを記憶している。
今まで時の流れを実感するのは自分の背丈くらいで、知り合う相手も同じくらいの身長だったので、この残酷さを意識することはそう多くなかったが、今は少し変わってきているようだ。
正直、もらったお年玉について、僕にはあまり使い道がないので、去年と同様に、どうせなら講師としての給料も合わせて全財産でビットコインでも買ってみようかと思う。
全損についてのリスクが大きくないのであれば、それ覚悟で数倍の利益を得るのが僕の考え方だ。
本宅の水槽やら植物やらを撮影した後、昨日書いた志望理由書の評価をした。
文章の細かい部分(主張や前後との文脈)を意識してみると、日本語としてより適切な表現が次から次へと見つかる。
プログラミング言語では一定のルールが敷かれているため、実装している言語のパラダイムに従っていれば(この言語のパラダイムは設計に合っていることを前提とする)、あからさまにおかしい部分をなくすのは、そう難しいものではない。
これは、あらゆる表現が繋がりを持って一つの文章を構成していることをより強く意識することになった。
午後3時くらいに別のタクシーで妹とマリオカートをしたりして空港へ向かった。
朝からあまり体調が良くなく、割と酔いそうだったので、ここでコーディングはしていない。
ただ、そのための前提知識を集めたりはした。
空港についたらすぐに保安検査場を通り、指定された搭乗口の近くで席に座って飛行機を待った。
前の飛行機がちょうど出発する時刻に到着し、そこでは妹にマインクラフトのレッドストーン回路で自動ドアを作る方法を聞かれた。
僕は妹が使っていたスイッチをしばらくの間借り、自動ドアとついでにエレベーターを作ったりした。
僕は少なくとも中学生のときからは似たようなことばかり取り組んでいる。
小学生の頃から航空機に対する関心はあって、最初にUnityで作ろうとしたゲームは航空機を作るゲーム(今の技術力でやっと実現できるレベル)だった。
電気回路(あまり触れていない)やC言語、bashを楽しみながら学ぶことができたのは、これらがそれぞれマインクラフトのコマンドやレッドストーン回路に相当していたからだと、その時僕は考えた。
今日乗った飛行機はB777-300だった。
昔は同じ経路でよく乗っていたり、少し前の韓国の事故などで意識していたB737ではなかったことに時の流れを感じた。
(これはシンプルに北海道への旅行客が増えただけの可能性もあり)
飛行機では何年か前に父からもらったソニーのノイズキャンセリングのヘッドフォンをつけて、iPhoneで先のコードを書いた。
ヘッドフォンは古いモデルなので、もう少しノイズキャンセリングできていいような気もしたが、十分コーディングに集中することができた。
また、モーターグライダーのパイロットとしては、「ジェット機の上昇力すっげぇなー」や「揺れの周期が全然違うなー」という感想を抱いた。
特に、モーターグライダーだと角度が30度以上の維持旋回をしている時にしか感じない、長時間のGの変動が直線飛行時に起こっていて非常に興味深かった。
これは旅客機の大きさや重さ、雄大さを感じることになった。
空港に到着したら、事前に予約していたレンタカーを借りるために空港から少し離れたレンタカー(借りる場所もレンタカーでいいの?)へそのレンタカーのバスで向かった。
形状は通常の公共のバスと同様なのにも関わらず、家族だけで独占していたので、若干の特別感が得られた。
レンタカーショップでの手続きは穏やかに進んだようなのだが、自腹で自販機でコーラを買おうとした時、小銭の量は多いのに、お金が足りるのかわからない状態になった。
レンタカーの自販機はなぜか、ゲームセンターのコインを投入する口のように入れやすくなっていたので、とりあえず財布の中身を一円玉すら省かずに投入した。
自動的に選別されるからいいだろという考え方だ。
正直、これはかなり後悔している。
140円まで入ったところで、自販機にいくら小銭を入れても認識しなくなった。
返却レバーを引いてみても、戻ってきたのは数十円。
何度、何度引いても、入れたはずの百円玉の姿はなかった。
なんとなく手持ちの十円玉を全て自販機に入れたところで、僕の小銭(200円くらい)は全て完全に自販機に吸い込まれ、ついぞ飲み物を得ることはできなかった。
流石に今からホテルに向かおうというところで自販機からお金を取り戻すわけにはいかなかったのだ。
Oh my….
父がレンタカーを運転している時に、「日本って左側車線だっけ」と冗談抜きで言ってきて、それに強く衝撃を受けた(物理的にも衝撃を受ける可能性が…)。
数年前にレンタカーを雪の壁にぶつけているので今年は勘弁してほしい(そのとき車に外傷はなかった)。
今日、結果としては2回ほどカーブでスリップし、一瞬ドリフト走行になった。
その後父は、このように言っていた。
「運転が下手な人はスリップをさらに悪化させて事故を起こす」
「お父さんは運転が上手いから事故らなかった」
だったらスリップしない運転をしてくれ。
マジで。
話は変わるが、今日はこの道中のローソンで二種類買ったはずのLチキが一つしかなかったことが心残りだ。