えいあーるれいの技術日記

ROS2やM5 Stack、Ubuntuについて書いています

記事上で煽ってたらアクロバティックなC++ (もはやbash) を提出された話

昨日の記事の続きです。↓

ar-ray.hatenablog.com


簡単に説明すると、コマンドラインからも実行可能&コンパイル可能なプログラムを公開したら、「shebangつけろ」というクソコメントが飛んできたのでプログラムの解説&煽る記事です。


すると、これを読んだ(つよつよ)学生プログラマがまさかのアクロバティックなスクリプトを提出してきました!


なんと、shebangが埋め込まれている!!!


プログラム

#!/bin/bash
#if NULL

THISFILE=`basename $0`
NAME=`basename $0 .cpp`

FILE=$(echo "#if NULL"; echo "#!/bin/bash"; tail -n +3 $THISFILE)
printf '%s\n' "$FILE" > $THISFILE
g++ -o $NAME $THISFILE
FILE=$(echo "#!/bin/bash"; echo "#if NULL"; tail -n +3 $THISFILE)
printf '%s\n' "$FILE" > $THISFILE
./$NAME
rm $NAME

exit
#endif

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
    return 0;
}

このスクリプトコンパイル不可能です。しかし、実行権限を付与した上で実行すると、bashで実行されます。

簡単な動作手順を示します。

  1. #!/bin/bash : bashで実行されます。
  2. #if NULL : 実はあまり意味がないが、C++からのコンパイル除外。bashではコメント扱い。
  3. THISFILE='basename $0' : ファイル名を取得
  4. NAME='basename $0 .cpp' : .cppを除いたファイル名を取得
  5. FILE=$(echo "#if NULL"; echo "#!/bin/bash"; tail -n +3 $THISFILE) : トリック(後述)
  6. printf '%s\n' "$FILE" > $THISFILE 先ほどのトリックの結果を用いてファイルを上書き
  7. g++ -o $NAME $THISFILE : コンパイル
  8. FILE=$(echo "#!/bin/bash"; echo "#if NULL"; tail -n +3 $THISFILE) : 5のトリックの逆
  9. printf '%s\n' "$FILE" > $THISFILE : 6の逆
  10. ./$NAME : 7のバイナリの実行
  11. rm $NAME : ファイルの削除
  12. exit : スクリプトの終了


次に5と8のトリックについて説明します。


トリック:シェル的文字列の入れ替え

5では、C++プログラムのコンパイルが通るように#if NULL#!/bin/bash を入れ替えます。


まず、$(echo "#if NULL"; echo "#!/bin/bash"; までの文で2行目を入力します。

次に、tail -n +3 $THISFILEで該当ファイルの3行目から末尾までを出力します。(=2行目までを除去)

そして、6行目の printf でコード整形してファイルに出力します。(これが無いと\nが出力されません)

コンパイルが終わったら、8, 9のトリックで元に戻します。


これを動画にするとこうなります。


まさにアクロバティック!!!

もはやC++じゃなくてBashファイルですが…


煽っていましたが…

煽りに対するこの回答は本質を突いているかどうかは怪しいところですが、こんな華麗かつ無駄なプログラムを提示されると馬鹿馬鹿しさを超えて美しさすら感じてしまいます。


アウトプットにツッコミを入れる際は流石にこのレベルほどではなくとも「おお!?」と思わせるアウトプットで返していきたいですね。


補足

tail -n +3 $THISFILE-n は不要みたいです。

さすがにshebangを2行目に書こうなんて人いないよね??(煽り

夕食を食べてる途中に思いついたC++bash実行アルゴリズム

「絶対コンパイルされない記述の中にインストール用スクリプト入れたら1つのファイルで完結するんじゃね??」

↑これを実装してTwitterに載せたところ、結構伸びました。


一方で、「shebangつけたほうがいい」などの意味不明なコメントも見られ、非常に頭にきたので本来は記事化する予定はなかったですが、記事にします。


再現コード

私が実行に使用したコードはこちらになります。(example.cpp)

#if NULL
THISFILE=`basename $0`
NAME=`basename $0 .cpp`

g++ -o $NAME $THISFILE
./$NAME
rm $NAME

exit
#endif

#include <stdio.h>

int main()
{
    printf("hello world\n");
}

このプログラムは非常にシンプルなHelloWorldのプログラムであり、普通は以下のように実行します。

g++ example.cpp -o example
./example

これをbashで解釈される性質を利用(悪用)して「実行可能な」C++ファイルを作成したわけです。

利用したのは#if NULL(0でも可)によるコンパイラからの除外です。


#if ~ #endif は条件付きコンパイルで、(マクロ含む)コンパイル不要な条件式の結果が真である場合にコンパイル対象に入れることができます。

そもそも定義されているかどうかによって分岐する #ifdef #ifndefなどもあり、プラットフォームやOSなどに応じて使い分けます。


このプログラムはbashで実行した際に次のように遷移していきます。

  1. #if NULL : コメントとして無視されます(C++では #endif まで無視されます。)
  2. THISFILE='basename $0' : 自分のファイル名 example.cpp を取得します(バッククォートを'に変えています)
  3. NAME=basename $0 .cpp` : .cppを取り除きます
  4. g++ -o $NAME $THISFILE : コンパイルします。 -o オプションで後ろに $NAMEexampleに置換)というバイナリを生成します。
  5. ./$NAME バイナリ ./$NAME を実行します。
  6. rm $NAME バイナリ $NAME を削除します。
  7. exit bashファイルとしての処理が終了します。
  8. #endif : 以降、C++ファイルが続きます。


この記法は # を無視するスクリプト言語の仕様と #if ~ #end によるコンパイル除外が可能なCの仕様を組み合わせてあたかもコンパイルなしで ./a.out ができる マジック のような

「懐かしい」と「面白い」を狙った投稿でした。


しかし、一部のプログラマセンスを疑うようなコメントがきて非常に腹が立ったので、ここで煽ろうと思います。

どんな投稿なのかはぜひ、上の伸びたツイートの引用RTをご覧ください。


指摘するならここだぞポイント

なぜ 「shebang を付けろ」がプログラマセンスを疑うような投稿であるかを以下に示します。

  1. C/C++の構造上、shebangが使えない
  2. .cpp の拡張子でbash実行ができるようなファイルは拡張子の意味がない。(←ネタであることが理解できていない、読解力不足)
  3. どうしてもbashで実行したいなら bash example.cpp でいいじゃん。shebangよりも明示的じゃない。


1は 1行目に #if NULL と競合するのでどう考えても無理です(C/C++側がコンパイルするため)。仮にできるなら教えてほしい。弟子にしてください。

2、3は言うまでもないでしょう。読みやすくするなら言語を混ぜていいわけがないですよね???

以上より、指摘するなら

「なにが実行可能なC++だよwwwww結局内部でbash動かしてコンパイルしてるだけじゃん」

です。shebangつけろコメントをした人は反省しなさい。


補足:shebangは2行目に書くと意味ない

macで実験しました。

maczshのターミナルですが、少なくとも私の環境では自動的にbashで実行されるようです。

↓これなら大丈夫


本当に変に伸びたツイートには必ずと言っていいほどクソリプがつくので相手にするのが面倒です。

…自戒の念も込めて。

RPi-Pico+PIOで<Wi-Fi.h>が混入するだけでエラーになる(理不尽な)現象

RaspberryPi Picoというボード、皆さんは使っていますか?

RaspberryPi PicoはRaspberryPi財団が独自に開発したマイコンチップおよびマイコンボードです。

デュアルコアのARM Cortex M0+がベースになっており、他の32bitマイコンと比べてとても低価格なのが魅力です。Linuxはインストールできず、C/C++SDKを叩くかMicroPythonを使用して開発を行うことが多いです。

私は、PlatfomIO(PIO)+Arduinoで使用することが多いです。

pico向けのHAT部品も多く発売されており、コミュニティも活発なようです。


移植中に…

最近、IoT関連のプロジェクトに首を突っ込んでいまして、元々ESP32で動いていたものをRaspberryPi Picoに移植しようとしています。

当然ながらESP32とRaspberryPi-Picoはコアが違うものなのでESP32の部分はif definedなどでコンパイル対象から除外してターゲットを切り替える必要があるのですが、そこで1時間程度詰まってしまったのです。

どうやらframework-arduino-mbed関連の部分で詰まっているようで、そのエラーログを見ながら検索したりしていたのですが、どうもうまく解決できませんでした。

キャッシュを削除してもこの問題は再現してしまい、かなりお手上げ状態でした…


一方で、他のボードが関係しないプロジェクトでプログラムをコンパイルしたところ、エラーが起こらず、正常に動くようになりました。

もう一度エラーログを見たところ、なぜかWiFiパッケージがdependencyに含まれていました。おそらくここらへんで詰まっているのでしょう。

現在使用しているのはEthernet付き・Wi-FiなしのRaspberryPi-Picoだったので、明らかにおかしな依存関係です。

W5100S-EVB-Picowww.switch-science.com

なお、この時点では <WiFi.h> をインクルードする記述はコメントアウトせずに #if definedコンパイル対象から除外するようにしていました。


PIOの仕様?

この <WiFi.h>コメントアウトしたところ、すんなりと通るようになりました。

なんと、コンパイル対象から除外していたとしてもPlatfomIO側?がWiFi.hを読んで依存関係を解決しようとするらしいです。

ちなみに、WiFi.h以外のESP32特有のライブラリはコメントアウトしなくても(lib_depsに含めなければ)依存関係に含まれません。

WiFiの問題はArduinoに標準で入っているからこそ起こっているのでしょう。

これはかなり困りますね…


解決法

platfomio.inilib_ignore = WiFi を追加してWiFiブラックリストに入れるだけで解決します。

# 追記例
lib_ignore =
    WiFi


RaspberryPi Picoへの移植時に元のパッケージに<WiFi.h>が含まれていたら要注意です!

rqt_graphを改造してmermaidも出力するようにしてみた

ROS使いであればお馴染みのGUIツールの一つに「rqt_graph」があります。

rqt_graphは、ROSのノード間の接続を確認することができるツールです。

プログラムをノードで管理するROSの特徴を的確に表しているため、これのスクリーンショットをしばしば貼られたりします。

今回は、このrqt_graphにできる限り近い形でmermaidに落とし込むプログラムを作成しました。


mermaidとは

Mermaidはテキストデータをもとにフローチャートやシーケンス図、クラス図を作成可能なツールです。

Markdownドキュメントと非常に相性が良いこともあり、GitHubにも機能が追加されています。

mermaid-js.github.io


mermaid化したいけど面倒→自動生成しよう!

rqt_graphの図形をREADMEに貼り付けるためにはスクショを撮って貼り付けるためのディレクトリを新規に作成して…が必要なので、できればmermaidにすると手軽に通信網を共有することができるでしょう。

しかし、mermaidの記法に慣れているわけでもないと使おうと考えすらしません(怠惰)

そこで、rqt_graphの情報をもとにmermaidも自動生成しようと考えていました。


とりあえずrqt_graphをフォークしてmermaidの出力に必要な接続情報を探すことに。

print出力を繰り返すうちに、描画する情報を集めるノード(dotcode.py)からedgesという情報が有効であることに気づきました。

github.com

edges はEdgeクラスのリストで、__slot__ で定義されている ['start', 'end', 'label', 'key', 'rkey', 'qos'] の要素を使用するとよさそうです。


これらのうち

  • start : トピック発行側
  • end : トピック購読側
  • label : トピック名

を使用してmermaid発行に使用することにしました。


リポジトリ

今回作成したリポジトリ「rmermaid_graph」は以下のリンクからクローン可能です。

github.com

rqt_graphと区別するために名前を変更しています。

ビルド後はrqt_graphと同じ使い方で

rmermaid_graph

と実行すれば動きます。


実行中は以下の画像のような出力が出ます。

https://raw.githubusercontent.com/Ar-Ray-code/rmermaid_graph/humble-devel/image_readme/mermaid.png


あとは、出力されたコードをGitHubやNotionなどに貼り付ければいいです。

トピックを絞りたい場合などは、rqt_graphのチェックボックスなどをつけてください。


更新予定

引数に渡されたテキストファイルにmermaidを書き出したり、GUIなしでも実行できるようにしたいです。

ぜひ、希望があればissueやプルリクエストを送ってもらえると助かります🙏

RTX3060Tiを購入しました。(2080Tiエピソード付き)

GPUを購入した記事を出してからほぼ2年後、新しいGPUを購入しました。

前の記事↓

ar-ray.hatenablog.com


2080Tiとの出会い、中古ガチャ(大)当たり

2年前、私はRTX2080Tiを購入しました。この頃は、RTX3000番台が近いと自作PC界隈がざわついていた頃でした。

この時は1万7000円程度で購入したGTX1650を使っていたのですが、研究などで使用する関係上どうしてもVRAMなどが足りず、困っていた時期でもありました。

研究室のコンピュータはみんな2世代も前のもので、正直学習はできるけど微妙…といった感じでした。


この頃はそれほど使えるお金もなかったので、格安のパーツがないかとフリマを見る機会が多かったのですが、その時9万円で出品されていた2080Tiが目に止まったのです。

RTX2080Tiは2018年に登場したゲーミング向けGPUの最高クラスでした。他の下位クラスは8GBのVRAMである一方で11GB VRAMを搭載しているのも特徴です。

そこらへんの性能のGPUについては9万前後のものは結構あったのですが、その中でも冷却性能の高く見た目もいい感じのROGのGPUで、当時同じ製品を新品で購入するなら18万は超えていたと思うのでかなり魅力的に感じたわけです。


中古品は自分が思っていたよりも悪いこともあるので、正直なところ運要素強めです。

しかし、手が出ないものが手に届きそうな感じがして、所持金ほぼ全額使って背伸びして購入することにしました。

GPUの状態は、ちょっとLED点灯に難あり程度で普通に動作してくれて売却する直前までしっかりと動作してくれたので当たりだったでしょう。

(しかも、この半年後くらいは在宅の流れやマイニングブームによって世界的にGPUが非常に手に入りづらい状況になり、GPUの相場も1.2倍程度上がりました。結果的に絶妙なタイミングでGPUを購入したことになりました。)


このGPU機械学習の学習環境が整えられたのはもちろんのこと、GPUが必要な開発において十分すぎる性能を与えました。

GPUが大いに役立った?記事

ar-ray.hatenablog.com

ar-ray.hatenablog.com

GPUが全てではないですが、現在の活動を支えた重要なパーツの一つと言えるでしょう。


売却。さすがに2世代前は…

このパーツは9月中旬に売却を行いました。

理由は、3つあります。

  1. 4年前のGPUだから
  2. RTX2080Tiが大きくて重すぎる
  3. 修復用GPUを持っておきたい


1は、中古を使っているがゆえに起こる不安です。5年、状態が良ければもっと使えるそうですが、GPUを酷使するとなると過去の使われ方を知らないがゆえにいつ壊れてもおかしくないという不安に駆られます。(意外にも中古品のデメリットをここで感じました。)

2は、自分が来年東京に行くからです。箱も大きいし重いので、3連ファンなので壊れて自分で処分する羽目になる前に売っておきたいと思いました。

3は、来年東京に行った後にデスクトップPCを2・3台組むので、その時にGPU交換時の繋ぎ止めに使えます。


結果、梱包・手数料などを差し引いて3万5千円で売れました。傷や年数を考慮するなら売却相場よりも少し高いくらいで売れました。


RTX3060Tiを選びました。

3070にするか、3050や1650などにするか非常に悩みましたが、いいとこ取りができる3060Tiを購入することしました。

3060Tiは大体6万5千円で購入でき、セールを狙えば5.5万も狙えるコスパの良いミドルレンジGPUです。


3060Tiは8GB RAMが搭載されたミドルレンジGPUですが、前世代のハイエンドGPURTX2080Superとほぼ同じ性能を出すことができると実測上言われています。VRAM、速度ともに2080Tiを持っているならダウングレードになります。

一方で、2080Tiを購入した当初の目的である「機械学習の環境」が現在必要ないことや購入から2年経過してVRや配信・重量級ゲームのプレイをしていないことを考慮すると、2080Tiと同等の性能はいらないことも薄々気付いていました。

正直なところGazebo/Unityシミュレーションとそこそこの物体検出ランタイムが動けばそれでいいとすら思っています。


そのため、「繋ぎ止め」を行うGPUに3060Tiを選定しました。3070はリリース日が1年違うことと消費電力で除外し、3060は12GB VRAMを搭載している分のコスパ悪化を考慮して除外しました。


RTX3060Ti届いた!

RTX3060Tiが届きました。

台風接近中に届けにきた配達の方、お疲れ様です…

早速組み立てました。

かなり小さい。これがRTX…?

200Wなので電源は8pinが一つだけです。


今回購入したものは、Mini ITXケースにも入るGPUなので、結構廉価版みたいな作りになっています。

ファンコントロールは効くものの、LEDの変更が効かなかったりします。仕方ない。(Win11使わないのであまり関係ないけど)


RTXかどうか怪しくなってきたので、とりあえずMinecraftをRTXしてみることに。

Minecraftのインストールを久しぶりにしたのですが、統合版しか買っていないのにJavaEditionも使えるようになっててびっくり。

www.minecraft.net

FHDならRTXしてくれました。ちゃんと60fpsは出そうです。

またマイクラやりたいなー。時間が無限にあれば…


darknet_rosもこの機会に実行してみました。

ちなみに、cudnn8が付いたDockerfileが新しく登場しているので、ちょっとDockerfileをいじりつつ実行してみることに。

↓リリースはこちらから

github.com


体感RTX2080Tiの80%くらいの性能でした。処理が軽ければ軽いほど性能差は出ないので、軽ければ問題ないくらいでした。


まとめ

GPUを新しく買った。 ハイエンドGPUを中古で購入して2年使ってまた中古で流したというお話でした。

売却額を考慮すると月2000円でRTX2080Ti使い放題プラン(※電気代別)を契約していたのとほとんど同じですね。(お得⁉︎)

機械学習周りは多くのネタのルーツになっているので、本当にいい買い物だと思います。