C言語でオレオレオブジェクト

概要

  • cのお勉強
    • c++ではない
    • C11をターゲットにする
      • C17ほどの新環境ではない
    • 本格的に開始して3日目の記事です
  • matlabで書けるやつをcに移行したい
    • オブジェクト指向の設計がちょっとできるようになった
    • 試しにcodegenを使ってみたけど納得できなかった…
  • どうしても組み込み開発にオブジェクト指向を使ってみたい…
    • 簡単なルーチンのコードならぶっちゃけ引き継ぎで確保している
    • handle classがとても便利だよね,ぜひともあれをやりたい
  • 結果,オレオレ自己参照構造体にオレオレ関数を設定する方針に至る
  • MBP-2016-15(Intel Core i7), macOS Monterey, Xcode CommandLine Tool clang
    • これ-std=c11にするとC11を基準にやってくれるらしい
    • はじめてmakefileをつくってみたけどかなりべんりだった

設計方針

  • 初心者なので最も簡単な事例を考える
    • 「依存関係を生成して変更に追従して参照できる」
  • main関数でハンドルオブジェクト的に使える
    • A.b();みたいなのは無理やわ
      • (実際やろうと思えばいけるっぽいけど設計も用法も無茶苦茶になりそうで流石に却下した)
    • b(A);みたいなので実装した
  • segmentation faultbus errorを吐かない
    • メモリ確保からメモリ解放まで一通り実践する

重要問題

  • callocを適切に設置しないと詰む
  • (少なくとも上記の環境では)オレオレ自己参照を含むオレオレ構造体のサイズをコンパイラがまともに推定してくれなかったので,自力でオレオレ構造体の容量を計算してアドレス空間を動的に生成した上でオレオレ自己参照に割り当てする必要がある
    • これはつまり仕様の変更に対して自動的に修正するコードパターンにしないとサイアクな目(連鎖的なセグフォ)に遭う
    • 仕様を記述した1つだけのstructexternして参照してメモリ確保を連鎖的に実装した(最適かどうかは知らない)
  • cは関数型の言語ゆえにmain()で手元でアドレスを取って引数に入れる手間をかけない限りどうやっても必ず値代入になる
    • 今回の縛りプレイの原因はほぼこれのせい,これを達成しながらどうにかして連鎖的な実装ができるように無茶苦茶やった
    • 魔改造でなんとかする方法があるなら知りたい,=を場合分けして強制的にアドレス代入にするとかやれば良いかもしれん
    • 普通に書いたら見た目はハンドルを握れたオブジェクトでも代入した時点で引数のコピーが生成されることになる
    • 抱える引数のアドレスがオブジェクトに割り当てるアドレス空間にならない,オブジェクトをコピーして所有して肥大化する

以下,出来上がったもの

#ifndef subPrm
#define subPrm


typedef struct Elm {
    int mem;
    int prv;
} Elm;


// parameter method

extern const Elm Prm;

typedef struct prm { //24
    struct prm *adr; //8
    long double num; //16
} prm;

prm defprm(long double num);

void setprm(prm theprm, long double num);

void delprm(prm theprm);

void expprm(prm theprm);

long double getprm(prm theprm);


#endif
  • prmでオレオレ構造体を定義する
  • prm *adrでオレオレ自己参照を定義する
  • ヘッダファイルはメンバ変数と関数宣言を置く
    • 仕様の大幅な変更がない限りはソースファイルだけで処理しよう
  • 関数定義はソースファイルに分ける
    • プロトタイプ宣言はヘッダファイルにおくという意味
  • ifndefタイプのインクルードガードを冒頭に設置する
  • Elmに仕様(拡張性がある)を持つ構造体を定義してPlmで公開する

他も似たような感じ

#include <stdlib.h>
#include <stdio.h>
#include "subPrm.h"


const Elm Prm = {
    .mem = 24
};

prm defprm(long double num){
    prm *adr = (prm*)calloc(1,Prm.mem);
    prm theprm = *adr;
    theprm.adr = adr;
    theprm.adr->num = num;
    return (theprm);
};

void setprm(prm theprm, long double num){
    theprm.adr->num = num;
};

void delprm(prm theprm){
    free(theprm.adr);
};

void expprm(prm theprm){
    printf("%Lf\n",theprm.adr->num);
};

long double getprm(prm theprm){
    return (theprm.adr->num);
};
  • prm *adr = (prm*)calloc(1,Prm.mem)で確保してprm theprm = *adr;に定義
  • free(theprm.adr)でメモリ解放(これってmatlabclearに相当するのでは?)
  • const Elm Prm = {.mem = 24};に仕様(メモリサイズ)を設定する
  • 使いたい関数をあらかじめ定義しておく(命名規則に注意)

他も似たような感じ

#ifndef subAxs2d
#define subAxs2d

#include "subPrm.h"


// axis method

extern const Elm Axs;

typedef struct axs2d { // 56
    struct axs2d* adr; //8
    prm px; //24
    prm py; //24
} axs2d;

axs2d defaxs2d(prm px, prm py);

void setaxs2d(axs2d xy, prm px, prm py);

void delaxs2d(axs2d xy);

void expaxs2d(axs2d xy);

long double getaxs2dx(axs2d xy);

long double getaxs2dy(axs2d xy);


#endif
#include <stdlib.h>
#include <stdio.h>

#include "subPrm.h"
#include "subAxs2d.h"


const Elm Axs = {
    .mem = 56
};

axs2d defaxs2d(prm px, prm py){
    axs2d *adr = (axs2d*)calloc(1,Axs.mem);
    axs2d xy = *adr;
    xy.adr = adr;
    xy.adr->px = px;
    xy.adr->py = py;
    return (xy);
};

void setaxs2d(axs2d xy, prm px, prm py){
    xy.adr->px = px;
    xy.adr->py = py;
};

void delaxs2d(axs2d xy){
    free(xy.adr);
};

void expaxs2d(axs2d xy){
    printf("(%Lf, %Lf)\n", getprm(xy.adr->px), getprm(xy.adr->py));
};

long double getaxs2dx(axs2d xy){
    return (getprm(xy.adr->px));
};

long double getaxs2dy(axs2d xy){
    return (getprm(xy.adr->py));
};
  • getprm()を連鎖的に仕様する,ポインタ地獄をある程度に回避できる
#ifndef subVec2d
#define subVec2d

#include "subAxs2d.h"


// vector method

extern const Elm Vec;

typedef struct vec2d { //152
    struct vec2d *adr; //8
    axs2d xy1; //56
    axs2d xy2; //56
    long double len; //16
    long double ang; //16
} vec2d;

vec2d defvec2d(axs2d xy1, axs2d xy2);

void setvec2d(vec2d v, axs2d xy1, axs2d xy2);

void delvec2d(vec2d v);

void expvec2d(vec2d v);

void calvec2d(vec2d v);


#endif
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "test7subAxs2d.h"
#include "test7subVec2d.h"


const Elm Vec = {
    .mem = 152
};

vec2d defvec2d(axs2d xy1, axs2d xy2){
    vec2d *adr = (vec2d*)calloc(1,Vec.mem);
    vec2d v = *adr;
    v.adr = adr;
    v.adr->xy1 = xy1;
    v.adr->xy2 = xy2;
    return (v);
};

void setvec2d(vec2d v, axs2d xy1, axs2d xy2){
    v.adr->xy1 = xy1;
    v.adr->xy2 = xy2;
};

void delvec2d(vec2d v){
    free(v.adr);
}

void expvec2d(vec2d v){
    long double x1 = getaxs2dx(v.adr->xy1);
    long double y1 = getaxs2dy(v.adr->xy1);
    long double x2 = getaxs2dx(v.adr->xy2);
    long double y2 = getaxs2dy(v.adr->xy2);
    printf("(%Lf, %Lf) -> (%Lf, %Lf)\n",x1,y1,x2,y2);
}

void calvec2d(vec2d v){
    long double x1 = getaxs2dx(v.adr->xy1);
    long double y1 = getaxs2dy(v.adr->xy1);
    long double x2 = getaxs2dx(v.adr->xy2);
    long double y2 = getaxs2dy(v.adr->xy2);
    v.adr->len = pow(pow((x2-x1),2)+pow((y2-y1),2),0.5);
    if (v.adr->len == 0){
        v.adr->ang = 0;
    } else {
        v.adr->ang = atan2((y2 - y1), (x2 - x1));
    };
    printf("len : %Lf, ang : %Lf\n",v.adr->len,(v.adr->ang)*180/M_PI);
};
  • getaxs2dx()getaxs2dy()を連鎖的に仕様する,ポインタ地獄をある程度に回避できる
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "test7subAxs2d.h"
#include "test7subVec2d.h"


int main(){

    double in;

    printf("x1 : ");
    scanf("%lf", &in);
    prm x1 = defprm(in);
    printf("rep : ");
    expprm(x1);
    printf("y1 : ");
    scanf("%lf", &in);
    prm y1 = defprm(in);
    printf("rep : ");
    expprm(y1);
    axs2d xy1 = defaxs2d(x1,y1);
    printf("axs xy1 : ");
    expaxs2d(xy1);

    printf("x2 : ");
    scanf("%lf", &in);
    prm x2 = defprm(in);
    printf("rep : ");
    expprm(x2);
    printf("y2 : ");
    scanf("%lf", &in);
    prm y2 = defprm(in);
    printf("rep : ");
    expprm(y2);
    axs2d xy2 = defaxs2d(x2,y2);
    printf("axs xy2 : ");
    expaxs2d(xy2);

    printf("vec xy1 -> xy2 : ");
    vec2d v = defvec2d(xy1, xy2);
    expvec2d(v);
    calvec2d(v);


    int amn;
    printf("fib-n order : ");
    scanf("%d", &amn);

    for (int i=0; i<amn; i++){
        if (0==(i%2)){
            setprm(x2, getprm(x2) + getprm(y2));
        }else{
            setprm(y2, getprm(x2) + getprm(y2));
        }
        printf("axs xy2 : ");
        expaxs2d(xy2);
    }
    calvec2d(v);


    return(0);
}

機能

  • 変更および参照が可能な変数を設定するprmオブジェクト
  • 2つのprmオブジェクトで位置座標を表現するaxs2dオブジェクト
  • 2つのaxs2dオブジェクトでベクトルを表現するvec2dオブジェクト
    • vec2dオブジェクトに向きと大きさを計算して与えるcalvec2d関数
    • 自動更新もちょっと考えたけど地獄が生まれるから無理だと判定
  • main()では以下を意図しているぜ
    • 数字入力の受付
    • オブジェクトの生成と代入
    • オブジェクトの連鎖的な生成
    • オブジェクトに固有の関数の実行
    • 下位オブジェクトの再代入と上位オブジェクトからの参照

コンパイル,実行

NAME    =   test.exe
SRMN    =   main.c
SRSB    =   subPrm.c subAxs2d.c subVec2d.c
OBMN    =   test.exe
OBSB    =   $(SRSB:%.c=%.o)
GCCC    =   clang
GCOP    =   -std=c11

make    :   $(SRMN) $(SRSB)
    $(GCCC) $(GCOP) $(SRMN) $(SRSB) -o $(OBMN)

いちいち全部コンパイルするので効率は悪い,もっと最適化できる

% make
% ./test.exe
x1 : 0
rep : 0.000000
y1 : 0
rep : 0.000000
axs xy1 : (0.000000, 0.000000)
x2 : 1
rep : 1.000000
y2 : 1
rep : 1.000000
axs xy2 : (1.000000, 1.000000)
vec xy1 -> xy2 : (0.000000, 0.000000) -> (1.000000, 1.000000)
len : 1.414214, ang : 45.000000
fib-n order : 70
axs xy2 : (2.000000, 1.000000)
axs xy2 : (2.000000, 3.000000)
axs xy2 : (5.000000, 3.000000)
axs xy2 : (5.000000, 8.000000)
axs xy2 : (13.000000, 8.000000)
axs xy2 : (13.000000, 21.000000)
axs xy2 : (34.000000, 21.000000)
axs xy2 : (34.000000, 55.000000)
axs xy2 : (89.000000, 55.000000)
axs xy2 : (89.000000, 144.000000)
axs xy2 : (233.000000, 144.000000)
axs xy2 : (233.000000, 377.000000)
axs xy2 : (610.000000, 377.000000)
axs xy2 : (610.000000, 987.000000)
axs xy2 : (1597.000000, 987.000000)
axs xy2 : (1597.000000, 2584.000000)
axs xy2 : (4181.000000, 2584.000000)
axs xy2 : (4181.000000, 6765.000000)
axs xy2 : (10946.000000, 6765.000000)
axs xy2 : (10946.000000, 17711.000000)
axs xy2 : (28657.000000, 17711.000000)
axs xy2 : (28657.000000, 46368.000000)
axs xy2 : (75025.000000, 46368.000000)
axs xy2 : (75025.000000, 121393.000000)
axs xy2 : (196418.000000, 121393.000000)
axs xy2 : (196418.000000, 317811.000000)
axs xy2 : (514229.000000, 317811.000000)
axs xy2 : (514229.000000, 832040.000000)
axs xy2 : (1346269.000000, 832040.000000)
axs xy2 : (1346269.000000, 2178309.000000)
axs xy2 : (3524578.000000, 2178309.000000)
axs xy2 : (3524578.000000, 5702887.000000)
axs xy2 : (9227465.000000, 5702887.000000)
axs xy2 : (9227465.000000, 14930352.000000)
axs xy2 : (24157817.000000, 14930352.000000)
axs xy2 : (24157817.000000, 39088169.000000)
axs xy2 : (63245986.000000, 39088169.000000)
axs xy2 : (63245986.000000, 102334155.000000)
axs xy2 : (165580141.000000, 102334155.000000)
axs xy2 : (165580141.000000, 267914296.000000)
axs xy2 : (433494437.000000, 267914296.000000)
axs xy2 : (433494437.000000, 701408733.000000)
axs xy2 : (1134903170.000000, 701408733.000000)
axs xy2 : (1134903170.000000, 1836311903.000000)
axs xy2 : (2971215073.000000, 1836311903.000000)
axs xy2 : (2971215073.000000, 4807526976.000000)
axs xy2 : (7778742049.000000, 4807526976.000000)
axs xy2 : (7778742049.000000, 12586269025.000000)
axs xy2 : (20365011074.000000, 12586269025.000000)
axs xy2 : (20365011074.000000, 32951280099.000000)
axs xy2 : (53316291173.000000, 32951280099.000000)
axs xy2 : (53316291173.000000, 86267571272.000000)
axs xy2 : (139583862445.000000, 86267571272.000000)
axs xy2 : (139583862445.000000, 225851433717.000000)
axs xy2 : (365435296162.000000, 225851433717.000000)
axs xy2 : (365435296162.000000, 591286729879.000000)
axs xy2 : (956722026041.000000, 591286729879.000000)
axs xy2 : (956722026041.000000, 1548008755920.000000)
axs xy2 : (2504730781961.000000, 1548008755920.000000)
axs xy2 : (2504730781961.000000, 4052739537881.000000)
axs xy2 : (6557470319842.000000, 4052739537881.000000)
axs xy2 : (6557470319842.000000, 10610209857723.000000)
axs xy2 : (17167680177565.000000, 10610209857723.000000)
axs xy2 : (17167680177565.000000, 27777890035288.000000)
axs xy2 : (44945570212853.000000, 27777890035288.000000)
axs xy2 : (44945570212853.000000, 72723460248141.000000)
axs xy2 : (117669030460994.000000, 72723460248141.000000)
axs xy2 : (117669030460994.000000, 190392490709135.000000)
axs xy2 : (308061521170129.000000, 190392490709135.000000)
axs xy2 : (308061521170129.000000, 498454011879264.000000)
len : 585967834257297.125000, ang : 58.282526

(1,1)スタートだとオーダーは70が限界,(0.1,0.1)スタートだとオーダーは80が限界 それ以上だとcalvec2d()infが発生してる,math.hでも2乗はキツいらしいな…

感想

  • 行列とか実装して演算も設計すれば自由度が広がりそう
  • でもやっぱりアドレス代入(&Obj)でいい気がしてきた
    • 32bitか64bitかでメモリサイズが変わる
    • OSとコンパイラでメモリサイズが変わる
    • const Elm Obj1={.mem=val1};val1コンパイル時に決定できないとエラーを吐く,例えば他のconst Elm Obj2={.mem=val2};val2参照できない
    • アドレスを羅列するだけならsizeof(void*)を参照先の数だけ用意すれば良いし,cに固有なデータ型のサイズはデフォで使えるはずだし,かえって環境や仕様の変化に強いのは
    • 結果的にメモリリーク対策がやりやすくなりそうなので次はそのバージョンもやってみる
      • main()を書くのがちょっと面倒になるけどしょうがない,c++使わせて欲しい

参考

http://www9.plala.or.jp/sgwr-t/lib/calloc.html

https://yu-nix.com/archives/c-object-extends/

https://programming-place.net/ppp/contents/c/037.html

https://c.perlzemi.com/blog/20210212090234.html

http://www.c-lang.org/self.html

https://dixq.net/forum/viewtopic.php?t=18103

上記に加えてTwitterの観測結果