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だったりする。若者は歳を取った時にも通用する技術って事で、些細な事より大きな枠で捉えて欲しいぞ。

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 のログインハックを再現してみた こういうのを見つつ、 今年の更新は、これで終わりです。来年も宜しくお願い致します。


This year's Index

Home