c++ (2)
振り返り
もう今年も終わり。久し振りに、あの人に連絡を取ってみたら、フィルターをやってるとか。 今は、半田ごてを振り回さなくても、フィルターを組める時代ですからね。思わず、オイラーも
アナログ視点で理解するディジタルフィルタ。FIRフィルタとIIRフィルタの構造とは
こういうの見て、フムフムしてる。
clang -v
c++がどのように働いているか、確認してみる。
[sakae@fb /tmp/cpp]$ c++ -v main.cpp FreeBSD clang version 11.0.1 (git@github.com:llvm/llvm-project.git llvmorg-11.0.1-0-g43ff75f2c3fe) Target: i386-unknown-freebsd13.0 Thread model: posix InstalledDir: /usr/bin "/usr/bin/c++" -cc1 -triple i386-unknown-freebsd13.0 -emit-obj -mrelax-all -disable-free -disable-llvm-verifier -discard-value-names -main-file-name main.cpp -mrelocation-model static -mframe-pointer=all -fno-rounding-math -mconstructor-aliases -munwind-tables -target-cpu i686 -fno-split-dwarf-inlining -debugger-tuning=gdb -v -resource-dir /usr/lib/clang/11.0.1 -internal-isystem /usr/include/c++/v1 -fdeprecated-macro -fdebug-compilation-dir /tmp/cpp -ferror-limit 19 -fgnuc-version=4.2.1 -fcxx-exceptions -fexceptions -fcolor-diagnostics -faddrsig -o /tmp/main-a2c5ce.o -x c++ main.cpp clang -cc1 version 11.0.1 based upon LLVM 11.0.1 default target i386-unknown-freebsd13.0 #include "..." search starts here: #include <...> search starts here: /usr/include/c++/v1 /usr/lib/clang/11.0.1/include /usr/include End of search list. "/usr/bin/ld" --eh-frame-hdr -dynamic-linker /libexec/ld-elf.so.1 --hash-style=both --enable-new-dtags -m elf_i386_fbsd -o a.out /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtbegin.o -L/usr/lib /tmp/main-a2c5ce.o -lc++ -lm -lgcc --as-needed -lgcc_s --no-as-needed -lc -lgcc --as-needed -lgcc_s --no-as-needed /usr/lib/crtend.o /usr/lib/crtn.o
色々なインクルードをインクルードしてるな。gccが顔を出しているぞ。ならば、その出来栄えは?
[sakae@fb /tmp/cpp]$ ldd a.out a.out: libc++.so.1 => /usr/lib/libc++.so.1 (0x2044e000) libcxxrt.so.1 => /lib/libcxxrt.so.1 (0x2050f000) libm.so.5 => /lib/libm.so.5 (0x2052d000) libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x2055f000) libc.so.7 => /lib/libc.so.7 (0x20577000)
特に指定しなくても、数学ライブラリィーが軒を並べるのか。そんじゃ、getlineがどんな風に取り込まれているか確認。ああ、元にしたのは前回やったCSVを読み込むやつね。
[sakae@fb /tmp/cpp]$ nm a.out | grep getline 00406e90 t _ZNSt3__17getlineIcNS_11char_traitsIcEENS_9allocatorIcEEEERNS_13basic_istreamIT_T0_EES9_RNS_12basic_stringIS6_S7_T1_EE 004069e0 W _ZNSt3__17getlineIcNS_11char_traitsIcEENS_9allocatorIcEEEERNS_13basic_istreamIT_T0_EES9_RNS_12basic_stringIS6_S7_T1_EES6_
なんか、関数名に余計な文字がくっついているな。同じ名前の関数が有った場合、引数の数とか型で区別するためのフラグみたいなものらしい。マングルって言う専門用語で呼ばれている。
[sakae@fb /tmp/cpp]$ nm a.out | grep getline | llvm-cxxfilt 00406e90 t std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(std::__1::basic_istream<char, std::__1::char_traits<char> >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&) 004069e0 W std::__1::basic_istream<char, std::__1::char_traits<char> >& std::__1::getline<char, std::__1::char_traits<char>, std::__1::allocator<char> >(std::__1::basic_istream<char, std::__1::char_traits<char> >&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, char)
そのマングルを平常語に直して表示してみた。型型してて、肩が凝るな。
debianでは、c++filt を利用する。マニュアルには
The C++ and Java languages provide function overloading, which means that you can write many functions with the same name, providing that each function takes parameters of different types. In order to be able to distinguish these similarly named functions C++ and Java encode them into a low-level assembler name which uniquely identifies each different version. This process is known as mangling. The c++filt [1] program does the inverse mapping: it decodes (demangles) low-level names into user-level names so that they can be read.
こんな説明が出てた。
sakae@pen:/tmp/cpp$ nm a.out | grep getline U _ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EE@GLIBCXX_3.4.21 U _ZSt7getlineIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RNSt7__cxx1112basic_stringIS4_S5_T1_EES4_@GLIBCXX_3.4.21 sakae@pen:/tmp/cpp$ nm a.out | grep getline | c++filt U std::basic_istream<char, std::char_traits<char> >& std::getline<char, std::char_traits<char>, std::allocator<char> >(std::basic_istream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)@GLIBCXX_3.4.21 U std::basic_istream<char, std::char_traits<char> >& std::getline<char, std::char_traits<char>, std::allocator<char> >(std::basic_istream<char, std::char_traits<char> >&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&, char)@GLIBCXX_3.4.21
ここまで踏み込む事は、ないだろうね。
printf
今、話題にしてるCSVのプログラムには、みんながお世話になる printfが使われている。それにも拘わらず、stdio.h をインクルードしていない。そんなのでいいのか?
[sakae@fb /tmp/cpp]$ cat main.cpp #include <fstream> //using namespace std; int main() { printf("Hello C++ world\n"); } [sakae@fb /tmp/cpp]$ c++ main.cpp [sakae@fb /tmp/cpp]$ ./a.out Hello C++ world
C言語が好きなのかCフラフラ語が好きなのか、腰が座っていないプログラムです。
[sakae@fb /tmp/cpp]$ c++ -E main.cpp | grep printf int fwprintf(FILE * , const wchar_t * , ...); int swprintf(wchar_t * , size_t n, const wchar_t * , int vfwprintf(FILE * , const wchar_t * , int vswprintf(wchar_t * , size_t n, const wchar_t * , int vwprintf(const wchar_t * , __va_list); int wprintf(const wchar_t * , ...); int fprintf(FILE * , const char * , ...); int printf(const char * , ...); :
<fstrema>の中でインクルードしてる誰かさんのインクルードに含まれているんだな。 調べてみたら、 <cstdio> とか <cstdlib> と言う、C言語の奴が含まれていた。こういう風にして、土地感を養っていくんだな。
vbox$ cd /usr/include/c++/v1/ vbox$ ls c* cassert ciso646 condition_variable cstring ccomplex climits csetjmp ctgmath cctype clocale csignal ctime cerrno cmath cstdarg ctype.h cfenv codecvt cstdbool cwchar cfloat compare cstddef cwctype charconv complex cstdint cxxabi.h chrono complex.h cstdio cinttypes concepts cstdlib
頭が c で始まるものは、昔からあるやつをCフラフラから使うみたいだ。と、言う事は、普段Cフラフラの積もりでいて、こまったらCって言うハイブリッドが便利って事かな。
Cフラフラの入門を読んで、構造体とクラスってほぼ同じ物って知見を得た。クラスの方は、隠す機能が付いてる。構造体の方は、あけっぴろげで、何も隠せない。
hoge.at(i)とhoge[i]の違いは、atは、インデックスの範囲外にアクセスすると、エラーになる。もう一方は、エラーチェック無しの昔からのC流儀だ。勿論余計な事をしない [] の方が速い。なお、 [] は、オペレーターの扱いとか。
それもこれも、ポインターのなせる技。ポインターを上手に隠した機能として、イテレーターが有るとな。なんだか、根底部分に触れた面持ちだな。
cmake
C++には標準のパッケージシステムって無いの? もどきは有るよ。この間やって、configureね。そんなムズイの普通に人には使えへん。で、作られたのがcmakeシステム。
sakae@deb:/tmp/cm$ cat hello.cpp #include <iostream> int main() { std::cout << "Hello, World!!\n"; return 0; } sakae@deb:/tmp/cm$ cat CMakeLists.txt project(hello) add_executable(hello hello.cpp)
ソースが有る。それをコンパイルするためのおまじないファイルを一つ用意する。ファイル名で悩まないように、CMakeLists.txtって決めうちになってる。DRYな原則ってやつ。
一行目はプロジェクト名。二行目は、そのプロジェクトにファイルを追加するよって指定。 これなら、素人でも用意出来るな。 準備はこれだけだ。
sakae@deb:/tmp/cm$ ls CMakeLists.txt hello.cpp sakae@deb:/tmp/cm$ mkdir build sakae@deb:/tmp/cm$ cd build sakae@deb:/tmp/cm/build$ cmake .. -- The C compiler identification is GNU 10.2.1 -- The CXX compiler identification is GNU 10.2.1 -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working C compiler: /usr/bin/cc - skipped -- Detecting C compile features -- Detecting C compile features - done -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ - skipped -- Detecting CXX compile features -- Detecting CXX compile features - done -- Configuring done -- Generating done -- Build files have been written to: /tmp/cm/build
後は、ビルド用のdirを用意して、その中でcmakeする。引数の .. は、マジックでも何でもなくて、親dir(にあるCMakeLists.txt)を指定してるだけ。これで、Makefile等が自動作成される。
sakae@deb:/tmp/cm/build$ make Scanning dependencies of target hello [ 50%] Building CXX object CMakeFiles/hello.dir/hello.cpp.o [100%] Linking CXX executable hello [100%] Built target hello sakae@deb:/tmp/cm/build$ ./hello Hello, World!!
後は、普通にmakeして実行するだけだ。広義のパッケージシステムって言ったら持ち上げ過ぎ?
c++ vs. python
最近の若者はLLに熱心なようだ。この場合のLLってのは、軽い言語って事じゃなくて、言語を勉強しましょって事ね。おじさん世代とは異ってるな。
【Python、GO、Rustがキーワードに】サポーターズ、トップエンジニア学生のプログラミング言語、開発環境における最新トレンドを大公開
一番人気はAIのあおりでPythonね。後は就職に有利なやつが続くとな。PythonにしろGoにしろ、誰かが作ったパッケージを取り込んできて、それを接着材で貼り合せるのが流行。時代の流れは、そうなってる。
で、オイラーは折角Cフラフラに手を出したんで、そのサンプルを少し眺めてみる。サンプルはよく分っている題材がいいな。見付けてきのがこれ。
Lisp interpreter in 90 lines of C++
(gdb) b proc_cons Breakpoint 2 at 0x555555556f52: file main.cpp, line 165. (gdb) c Continuing. (cons 12345 6789) Breakpoint 2, proc_cons (c=std::vector of length 2, capacity 2 = {...}) at main.cpp:165 165 cell result(List); (gdb) bt #0 proc_cons (c=std::vector of length 2, capacity 2 = {...}) at main.cpp:165 #1 0x000055555555877d in eval (x=..., env=0x7fffffffe360) at main.cpp:240 #2 0x0000555555559148 in repl (prompt="90> ", env=0x7fffffffe360) at main.cpp:325 #3 0x0000555555559274 in main () at main.cpp:332
うん、ちゃんと動いているな。そんじゃ、環境の調査。
(gdb) p *env $3 = { env_ = std::map with 17 elements = { ["#f"] = { type = Symbol, val = "#f", list = std::vector of length 0, capacity 0, proc = 0x0, env = 0x0 }, : ["*"] = { type = Proc, val = "", list = std::vector of length 0, capacity 0, proc = 0x555555556765 <proc_mul(std::vector<cell, std::allocator<cell> > const&)>, env = 0x0 },
これの意味する所は?
struct cell { typedef cell (*proc_type)(const std::vector<cell> &); typedef std::vector<cell>::const_iterator iter; typedef std::map<std::string, cell> map; cell_type type; std::string val; std::vector<cell> list; proc_type proc; environment * env; cell(cell_type type = Symbol) : type(type), env(0) {} cell(cell_type type, const std::string & val) : type(type), val(val), env(0) {} cell(proc_type proc) : type(Proc), proc(proc), env(0) {} };
最初に定義されてる typedef が、オイラーの頭を混乱させてた。
今度は、試験プログラムを走らせる。回答を間違ったものにして、フェイルするか確認。
// the 29 unit tests for lis.py TEST("(quote (testing 1 (2.0) -3.14e159))", "(testing 1 (2.0) -3.14e159)"); TEST("(+ 2 2)", "5"); TEST("(+ (* 2 100) (* 1 10))", "210");
来月は試験が有るけど、くれぐれも間違った正答で採点しないようにね。許されるのは、こういう場面だけだからね。
sakae@deb:/tmp/cpp$ make g++ -std=c++17 -Wall --pedantic-errors -g -include all.h main.cpp -o a.out ./a.out main.cpp(362) : expected 5, got 4 total tests 29, total failures 1 make: *** [Makefile:4: run] Error 1
ちゃんとエラーを検出した。興味有る所は、これを実現してる部分。
template <typename T1, typename T2> void test_equal_(const T1 & value, const T2 & expected_value, const char * file, int line) { ++g_test_count; if (value != expected_value) { std::cout << file << '(' << line << ") : " << " expected " << expected_value << ", got " << value << '\n'; ++g_fault_count; } } #define TEST_EQUAL(value, expected_value) test_equal_(value, expected_value, __FILE__, __LINE__) #define TEST(expr, expected_result) TEST_EQUAL(to_string(eval(read(expr), &global_env)), expected_result)
旨いなあ。#defineで、マクロを定義して詳細は隠す。試験問題を実行するのに、実質REPL を使うとな。そして、エラーが有った場合に、ファイル名やら行番号を出すのに、テンプレートを使っている。なんたって、以前にやった、m4 をここに持ち込めないからね。
ついでに、試験問題で、zipも出題されてた。
TEST("(define combine (lambda (f)" "(lambda (x y)" "(if (null? x) (quote ())" "(f (list (car x) (car y))" "((combine f) (cdr x) (cdr y)))))))", "<Lambda>"); TEST("(define zip (combine cons))", "<Lambda>"); TEST("(zip (list 1 2 3 4) (list 5 6 7 8))", "((1 5) (2 6) (3 7) (4 8))");
これをCフラフラで簡単に記述出来るのかな。頭が回らん。
scheme in C++
そこで、もう一発、別解も見て億。
Implementing Scheme in C++ - Introduction
schm >>>(+ 1111 2222) Breakpoint 1, add (vv=std::vector of length 2, capacity 2 = {...}) at Procedures.cpp:25 25 if (vv.size() == 0)return "Wrong number of arguments for procedure +"; (gdb) p vv $3 = std::vector of length 2, capacity 2 = {"1111", "2222"}
どうも、文字列で受渡ししてるな。一応確認しとく。
//Add one or more variables; e.g. (+ aa), (+ aa bb cc) string add(vector<string>&vv) { B => if (vv.size() == 0)return "Wrong number of arguments for procedure +"; stringstream ss; double sum = strtod(vv[0].c_str(), NULL); for (size_t i = 1; i < vv.size(); i++)sum += strtod(vv[i].c_str(), NULL); ss << sum; return ss.str(); }
こういう実装も有りなんだな。
(gdb) bt #0 add (vv=std::vector of length 2, capacity 2 = {...}) at Procedures.cpp:25 #1 0x00483c77 in Object::apply (this=0x2009bd8, V=std::vector of length 2, capacity 2 = {...}) at Object.cpp:58 #2 0x004865b4 in eval (pp=..., env=std::map with 19 elements = {...}) at schm.cpp:176 #3 0x00487829 in REPL (env=std::map with 19 elements = {...}) at schm.cpp:225 #4 0x00488844 in main () at schm.cpp:266
(gdb) p env $9 = std::map with 19 elements = { ["+"] = { pp = 0x48199e <add(std::vector<std::__cxx11::basic_string<char, std::char_t\ raits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<\ char, std::char_traits<char>, std::allocator<char> > > >&)>, proc = { arg_list = { store = std::vector of length 0, capacity 0 }, body = { store = std::vector of length 0, capacity 0 } }, value = "", kind = "procedure", nta ive_proc = true },
今度は、関数を登録してみる
schm >>>(define fuga (lambda (n) (+ n n n))) schm >>>(fuga 1111) 3333
なる程、こんな風に登録されるとな。
["fuga"] = { pp = 0x0, proc = { arg_list = { store = std::vector of length 1, capacity 1 = {"n"} }, body = { store = std::vector of length 9, capacity 9 = {"(", "begin", "(", "+", "n", "n", "n", ")", ")"} } }, value = "", kind = "procedure", native_proc = false },
ついでに、変数も
["foo"] = { pp = 0x0, proc = { ... }, value = "12345", kind = "variable", native_proc = false },
rust
CフラフラはベターCと良く言われる。英語の比較級だな。ならば、ベストCは何になる?
虎視眈々とその座を狙っているのは、GoでありRustだったりする。若者は歳を取った時にも通用する技術って事で、些細な事より大きな枠で捉えて欲しいぞ。
これなんか、Cフラフラの進化系だな。
オイラーは、ちょいと読んでいた小説に、樹里亜 なんて名前の女性が出てきていたので、反射的に、juliaを入れた所だ。いつの間にかupdateされてた。
sakae@deb:/tmp$ /usr/local/julia/bin/julia _ _ _ _(_)_ | Documentation: https://docs.julialang.org (_) | (_) (_) | _ _ _| |_ __ _ | Type "?" for help, "]?" for Pkg help. | | | | | | |/ _` | | | | |_| | | | (_| | | Version 1.7.0 (2021-11-30) _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release |__/ | julia>
そして、 自作Cコンパイラで Ken Thompson のログインハックを再現してみた こういうのを見つつ、 今年の更新は、これで終わりです。来年も宜しくお願い致します。