最近のトラックバック

最近のコメント

ウェブページ

2017年8月
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

他のアカウント

無料ブログはココログ

カテゴリー「パソコン・インターネット」の記事

2017年6月25日 (日)

C++ で自分自身の型を表す方法や、thisポインタの偽物作りとかやってみた

C++言語には、「自分自身の型」を表す標準的な記法がない。C++11 からは decltype(*this) で可能になったが、自由に使えるわけではない。例えば、メンバ関数の外側で

using ThisClass = decltype(*this) ;

などのような使い方はうまくできない。

また、Fooというクラスの中では decltype(*this) は Foo& 型なので、Foo型を使いたければ参照をはずさなければならない。

そこで、少々トリッキーでもよいからそのような方法を考えてみた。とは言っても、自分の力では無理なので、あちこち検索した結果の切り貼り組み合わせが主である。

プログラムの流れは 自分の型を取得 を参照した。

C++:自クラスの型にアクセスできる方法はないでしょうか?に、メンバ関数外で自分自身の型を表す名前を取得する方法として、static関数宣言を利用するトリックがあったので、これで一応できることが分かった。が、意味的におかしいので正統的な方法ではない。コンパイルエラーとすべきもの。
また、これが継承で伝播されるか?と一応試してみたが、予想通り伝播しなかった。

さらに、「thisポインタの偽物」を作ると const_cast や mutable を使わなくともconst性を弄れるので、それも追加した。これは自作。

これは、

class Foo{
....
private:
Foo* const m_mythis = this;
public:
Foo* const mythis(){ return m_mythis;}
....
};

ということをやると、const Foo x; としても、*(x.mythis()) はxを表すがconst性は持たない、という代物である。(g++では動いたが、コンパイラによって挙動が変わるかもしれない。)

これは簡単にできるので面白くなかったのだが、むしろこんな簡単にトリッキーなコードが書けるのか!と、C++の恐ろしさを感じた。
const_castやmutable が書いてあればまだ「const性を弄っているな」と分かるが、それなしでconst性が外せるのは、うっかりミスが怖い。

この偽物のthisを使うと「const な代入演算子」などが定義できる。

以下のコードは Ubuntu 上 g++ ver.4.8.4 (-std=c++11 オプション付き)で通った。

ただし、clang++ では「staticメンバ関数の宣言に this は使えませんよ!」と叱られてエラーとなる。

つまり、g++の型推論のチェックの甘さを利用したコードになるので、当然、このコードを実行した場合の結果にはまったく責任が取れないことになる。

ソース:

//thisclass-main.cpp
#include <iostream>

class Foo{
public:
virtual ~Foo() = default;
Foo(){}
Foo( const Foo& f ){ std::cout<< "コピーコンストラクタ" << std::endl;}
Foo( Foo&& f ){ std::cout<< "ムーブコンストラクタ" << std::endl;}
Foo& operator=( const Foo& f ){
std::cout << "代入" << std::endl;
return *this;
}
Foo& operator=( Foo&& f ){
std::cout << "ムーブ代入" << std::endl;
return *this;
}

static auto dummythis()->decltype(this); //宣言のみ
// Foo* 型を表す型
using This = decltype(dummythis());

static auto dummythisref()->decltype(*this);//宣言のみ
// Foo& 型を表す型
using ThisRef = decltype(dummythisref());
// Foo 型を表す型
using ThisClass = std::remove_reference<ThisRef>::type;

ThisClass myself(){ return *this;}
// トップレベルで ThisClass 使用

private:
This const m_mythis = this;
// thisポインタの「偽物」
public:
This const mythis() const {return m_mythis;}

// m_mythis はポインタなので、 const Foo x; としても
// *(x.mythis()) に const性はない。
// const 性はポインタによって伝播されないため。
// const_cast や mutable を使わずにconst性を除去可能。
// (コンパイラによって挙動が異るかもしれない)。
// これ自身は自分の型を表す名前作成とは独立の話。

// const な代入演算子が可能。通常の代入演算子とオーバーロードされる。
Foo& operator=( const Foo& f ) const {
std::cout << "const ";
*m_mythis = *(f.mythis());
return *m_mythis;
}

virtual void foo(){
ThisClass::bar(); //この書き方が可能
}

static void bar(){
std::cout << "bar" << std::endl;
}
};


class Deriv : public Foo{
public:
virtual void foo() override {
// ThisClass::bar2();
// 「Foo::ThisClass に bar2メンバ関数がない」エラー
// 継承したからといってThisClass がDeriv型になるわけではない。
}

static void bar2(){
std::cout << "bar2" << std::endl;
}
};


int main(){
Foo().foo();
Foo().myself().foo();
const Foo x;
Foo y;
x = y; // const 代入!
Deriv().bar2();
return 0;
}

実行結果:

bar
コピーコンストラクタ
bar
const 代入
bar2

以上。

2017年3月27日 (月)

c++ でconst修飾をしても非constメンバ関数を呼べるトリッククラス。思いつき

c++ のstd::string は、constメンバ関数では本体の文字列が変更されることはない、という仕様がある。
このために、 const std::string では文字列の中身が変更されることはない。
まあ、c++にはmutable とか const_cast とかあるけれども。

そこで、ちょっと思いつきがあって、std::stringにちょっとだけ仕掛けをした拡張クラスを作ってみたら、const修飾してもstd::stringの非constメンバ関数を呼び出せてしまった。(もちろん、std::stringの拡張クラスでできた、というだけで const std::string が文字列を変えないことには違いない)。

追記: STLコンテナのデストラクタはvirtualではないと後で知りました。すると、std::string などの public な継承クラスを作るのは間違いですね。

ソース:
----

#include <iostream>
#include <string>

class mystring : public std::string{
private:
mystring* const pseudothis_ = this;
public:
mystring* pseudothis() const {return pseudothis_;}
};

int main(){
const mystring str;
std::cout << "str=" << str << std::endl;
str.pseudothis()->append("abc");
std::cout << "str=" << str << std::endl;
*(str.pseudothis()) += "def";
std::cout << "str=" << str << std::endl;
return 0;
}

----

実行結果
----

str=
str=abc
str=abcdef

----

手抜きクラスなので、自動生成された(空文字列で初期化の)デフォルトコンストラクタしかないが、const 修飾されてもstd::stringの非メンバ関数を呼び出せるため文字列の変更ができる。

しかし、ちょっと待て。これはstd::string の拡張クラスでなくても pseudo_ メンバ(とそのゲッター)のあるクラスを作れば、const修飾しても非constメンバ関数を呼び出せるクラスになるのではないか? c++の言語仕様でこのことが「保障」されるかどうかは断言できないが、かなり普遍性のあるトリックになりそうだ。
(もちろん、 class T {....} の中で T* const pseudothis _= this; というメンバを作るわけだ。)

2016年11月19日 (土)

C++ でのconst性ってどこまで保証されてる?

大昔C言語を少しかじったことがある。

C++は自分でメモリ管理をするのはちょっときついと、勉強しなかった。ただし、CとC++の非互換性の部分はチェックしたが。

今でも、特にC++でプログラムする必要はないのだが、教養として少しずつ勉強をしている(かたつむりのように鈍く)。

C++ では const はとても大事だ。
しかし、const宣言では何が保証されるのだろうか?

この疑問はスマートポインタの一種のshard_ptr の動きから生まれた。

クラスの中で

const std::shared_ptr<T> p;

と「定数」pをつくっても、p.use_count() の値は変化しえる。というか変化できないと困る。

long getUseCount() const { return p.use_count();}

というconstメンバ関数を作ることはできるが、この値は変化し得るわけだ。
これは大丈夫なのか?といろいろ検索した結果、constメンバ関数が保証するのは const *this である、と覚えればいいと分かった。
つまり、クラスの中の変数の変化がないだけであって、ポインタ変数の参照先は書き換えることができる。(またstatic変数も書き換えてよい)。

shard_ptr も参照カウンタはおそらくポインタを使って共有されているから書き換え可能で問題はない。use_count() は変化してよい。これで納得。

・・・が、それは本当か? 例えば、const 参照引数されたクラスのインスタンスで非スタティック/非ポインター・メンバー変数が書き換えられたりしないのか?mutable とか const_cast など使わずに。

ちょいと実験してみた。

Foo.h
----------------

#ifndef FOO_H_
#define FOO_H_

// コピーコンストラクタの引数のconst性を破壊できるか?をテストするクラス。
class Foo{
private:
int counter_ = 0; //コピー・ムーブの元の場合、自分がコピーされた回数を数える
// つもりで最初は設定した。
// しかし、コピー・ムーブの仕方によってはリセットされるなどがあり得るので
// この変数が正確には何を数えているのか作者にもよく分からない。

int* pcounter_ = &counter_;
// 初期状態ではcounter_の位置。書き換えられ得るので注意


public:
Foo(){}
Foo( const Foo& f){
counter_ = f.counter_;

pcounter_ = f.pcounter_;
// this->pcounter_ は f.pcountrのコピーなので
// this->counter_ を参照しない。
// f.pcounter_ も書き換えられてる可能性があるので静的にpcounter_の参照先は決定できない
// *this が生きているうちにfの寿命が尽きたりなどすると簡単にダングリングポインタに
// なるので非常に危険

++*pcounter_;
// f.pcounter_ が f.counter_ を参照している場合はf.counter_ を1増加させる。
// int 型の f.counter_ を書き換えるとするとconst性を壊す可能性がある。
// そうでない場合も、どれかのFooインスタンスのcouter_変数を増加させる。
// ただし、pcounter_ がダングリングポインタになってる場合は何が起きるか分からない。
// 以下のムーブコンストラクタや=のオーバーロードなどでも同じ問題がある。
}
Foo( Foo&& f){
// ちょっとムーブっぽく、f.pcounter_ が消えても大丈夫なようにしておく。大した意味はない。
counter_ = *f.pcounter_;
f.pcounter_ = nullptr;
pcounter_ = &counter_;
++counter_;
}
Foo& operator=(const Foo& f){
counter_ = f.counter_;
pcounter_  = f.pcounter_;
++*pcounter_;
return *this;
}
Foo operator=(Foo&& f){
// ちょっとムーブっぽく、f.pcunter_ が消えても大丈夫なようにしておく。大した意味はない。
counter_ = *f.pcounter_;
f.pcounter_ = nullptr;
pcounter_ = &counter_;
++*pcounter_;
return *this;
}
virtual ~Foo(){}

// ゲッターメソッド
const int& counter() const {return counter_;}
int deref_pcounter() const {return *pcounter_;}

};

#endif // FOO_H_

--------

pcounter_ ポインタメンバ変数がメンバ変数 counter_ を参照している。こういうことをするといろいろ危険である。

テスト・プログラム

Foo.cpp
----------------

#include <iostream>

#include "foo.h"

Foo idFoo( Foo x){ return x;}

int main(){
Foo a;
std::cout << "Foo a;" << std::endl;
std::cout << "a.counter()="<< a.counter() << std::endl;
std::cout << "a.deref_pcounter()="<< a.deref_pcounter() << std::endl;

Foo b = a; // 初期化
std::cout << std::endl << "Foo b = a;" <<std::endl;
std::cout << "a.counter()=" << a.counter() << std::endl;
std::cout << "a.deref_pcounter()="<< a.deref_pcounter() << std::endl;
std::cout << "b.counter()=" << b.counter() << std::endl;
std::cout << "b.deref_pcounter()="<< b.deref_pcounter() << std::endl;

Foo c;
c = b; // 代入
std::cout << std::endl << "Foo c; c=b;" << std::endl;
std::cout << "a.counter()=" << a.counter() << std::endl;
std::cout << "a.deref_pcounter()="<< a.deref_pcounter() << std::endl;
std::cout << "b.counter()=" << b.counter() << std::endl;
std::cout << "b.deref_pcounter()="<< b.deref_pcounter() << std::endl;
std::cout << "c.counter()=" << c.counter() << std::endl;
std::cout << "c.deref_pcounter()="<< c.deref_pcounter() << std::endl;

Foo d;
c = a; // c の書き換え
a = d; // a の書き換え
std::cout << std::endl << "Foo d;c = a; a = d;" << std::endl;
std::cout << "a.counter()=" << a.counter() << std::endl;
std::cout << "a.deref_pcounter()="<< a.deref_pcounter() << std::endl;
std::cout << "b.counter()=" << b.counter() << std::endl;
std::cout << "b.deref_pcounter()="<< b.deref_pcounter() << std::endl;
std::cout << "c.counter()=" << c.counter() << std::endl;
std::cout << "c.deref_pcounter()="<< c.deref_pcounter() << std::endl;
std::cout << "d.counter()=" << d.counter() << std::endl;
std::cout << "d.deref_pcounter()="<< d.deref_pcounter() << std::endl;

Foo e = idFoo(a);
std::cout << std::endl << "Foo e = idFoo(a);" << std::endl;
std::cout << "a.counter()=" << a.counter() << std::endl;
std::cout << "a.deref_pcounter()="<< a.deref_pcounter() << std::endl;
std::cout << "e.counter()=" << e.counter() << std::endl;
std::cout << "e.deref_pcounter()="<< e.deref_pcounter() << std::endl;



return 0;
}

--------

実行結果:

--------
Foo a;
a.counter()=0
a.deref_pcounter()=0

Foo b = a;
a.counter()=1
a.deref_pcounter()=1
b.counter()=0
b.deref_pcounter()=1

Foo c; c=b;
a.counter()=2
a.deref_pcounter()=2
b.counter()=0
b.deref_pcounter()=2
c.counter()=0
c.deref_pcounter()=2

Foo d;c = a; a = d;
a.counter()=0
a.deref_pcounter()=1
b.counter()=0
b.deref_pcounter()=0
c.counter()=2
c.deref_pcounter()=0
d.counter()=1
d.deref_pcounter()=1

Foo e = idFoo(a);
a.counter()=0
a.deref_pcounter()=2
e.counter()=3
e.deref_pcounter()=3
--------

わけの分からない結果が出たが、どうやらFooクラスのインスタンスの int型変数 counter_ はコピーコンストラクタのコピー元で const が付いていても変化するようだ。

idFoo() 関数は値渡しなので、コピー回数が増えたようだ。

・・・まあ、C++もC言語の流れを汲んでるから、仕方ないな。

追記: ムーブコンストラクタ等、少々書き換えました。ダングリングポインタが出る可能性は全然なくなってませんが。

追記2: C++に詳しい人々に聞いたら、単に未定義動作をするプログラムを作っただけ、というだけの話のようです。

2016年8月13日 (土)

ある「プロ」の書いた、初心者向けc言語入門プログラム

昔、ある所で、「俺はc言語が得意」と言っているプロ(既に、プログラマーではなく、プロジェクト管理とかやっている人らしい)が、プログラム初めての初心者向けに書いたサンプルソースを見て、目が点になったことがある。
初心者向けとは見えないし、このプロ氏は仕事でどんなプログラムを作っているのか怖くなった。

sample.h
------------

/** 2重インクルード防止を付けて欲しい **/

// ローマ字カナ変換処理
int Romaji2Kana(
char *Src,
char *Buff
);
// 半角文字列大文字変換処理
void Str2Upper(
char *Buff
);
// 改行コード削除処理
void RemoveCRLF(
char *Buff
);

sample.cpp
---------------

/************************************************************************************
* 「C言語が得意」というプロ氏の書いた「プログラム未経験の初心者用ソース。           *
* 若干の書き換えはあるが、ほとんどそのまま。                                        *
* 「//」形式のコメントは、プロ氏の書いたコメント。「/*」形式のコメントはブログ      *
* 主のコメント。                                                                 *
*                                                                                   *
* ファイル名は書き換えてあるが拡張子がcppとなっているのはそのまま。                 *
* マイクロソフト Visual Studio の Visual C++ では、                                 *
* ソースファイル名の拡張子を省略した場合、C++の拡張子である「.cpp」が               *
* 付けられてしまう。                                                                *
* つまり、C言語プログラムのつもりでもC++としてコンパイルされる。                    *
* 「C++はCの上位互換」という迷信があるが、CとC++ は非互換なので                     *
* Visual C++ で純粋なC言語ソースを書きたい場合はソースファイル名には                *   
* 拡張子「.c」を明示するべき。                                                      *
* 私「gccの場合だと・・・」と言い出したら、プロ氏は「gccの話なら別の機会にしてくれ」*
* とキレた。もしかして、Windows PC上でgcc環境の構築ができないのか?                 *
*************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <memory.h>

// オリジナルヘッダ定義
#include "sample.h"

/* C++としてコンパイルされるわけだし、constがあった方がいいと思う */
//  ローマ字変換テーブル(グローバル変数)
char        *RMojiTbl[ 175 ][ 2 ] = {
    //  あ行
    {"A", "ア" },    {"I", "イ" },    {"U", "ウ" },    {"E", "エ" },    {"O", "オ" },
    //  か行
    {"KA", "カ" },   {"KI", "キ" },   {"KU", "ク" },   {"KE", "ケ" },   {"KO", "コ" },
    {"KYA", "キヤ" }, {"KYI", "キイ" }, {"KYU", "キユ" }, {"KYE", "キエ" }, {"KYO", "キヨ" },
    //  さ行
    {"SA", "サ" },   {"SHI", "シ" },  {"SU", "ス" },   {"SE", "セ" },   {"SO", "ソ" },
    {"ZI", "ジ" },  {"SI", "シ" },   {"JYO", "ジヨ" },    {"KEN", "ケン" }, {"OH", "オウ" },
    {"SHA", "シヤ" }, {"SYI", "シイ" }, {"SHU", "シユ" }, {"SHE", "シエ" }, {"SHO", "シヨ" },
    //  た行
    {"TA", "タ" },   {"CHI", "チ" },  {"TSU", "ツ" },  {"TE", "テ" },   {"TO", "ト" },
    {"CHA", "チヤ" }, {"CYI", "チイ" }, {"CHU", "チユ" }, {"CHE", "チエ" }, {"CHO", "チヨ" },
    {"TOH", "トウ" }, {"TI", "チ" },   {"TU", "ツ" },   {"TTO", "ツト" }, {"TYO", "チヨ" },
    {"THA", "チヤ" }, {"THI", "チイ" }, {"THU", "チユ" }, {"THE", "チエ" }, {"THO", "チヨ" },
    //  な行
    {"NA", "ナ" },   {"NI", "ニ" },   {"NU", "ヌ" },   {"NE", "ネ" },   {"NO", "ノ" },
    {"NYA", "ニヤ" }, {"NYI", "ニイ" }, {"NYU", "ニユ" }, {"NYE", "ニエ" }, {"NYO", "ニヨ" },
    //  は行
    {"HA", "ハ" },   {"HI", "ヒ" },   {"FU", "フ" },   {"HE", "ヘ" },   {"HO", "ホ" },
    {"HYA", "ヒヤ" }, {"HYI", "ヒイ" }, {"HYU", "ヒユ" }, {"HYE", "ヒエ" }, {"HYO", "ヒヨ" },
    {"FA", "フア" },  {"FI", "フイ" },  {"FE", "フエ" },  {"FO", "フオ" },  {"SYU", "シュ" },
    {"FYA", "フア" }, {"FYU", "フユ" }, {"FYO", "フヨ" }, {"HU", "フ" },   {"DUMMY", "DUMMY" },
    //  ま行
    {"MA", "マ" },   {"MI", "ミ" },   {"MU", "ム" },   {"ME", "メ" },   {"MO", "モ" },
    {"MYA", "ミヤ" }, {"MYI", "ミイ" }, {"MYU", "ミユ" }, {"MYE", "ミエ" }, {"MYO", "ミヨ" },
    //  や行
    {"YA", "ヤ" },   {"YU", "ユ" },   {"YO", "ヨ" },   {"DUMMY", "DUMMY" },    {"DUMMY", "DUMMY" },
    //  ら行
    {"RA", "ラ" },   {"RI", "リ" },   {"RU", "ル" },   {"RE", "レ" },   {"RO", "ロ" },
    {"RYA", "リヤ" }, {"RYI", "リイ" }, {"RYU", "リユ" }, {"RYE", "リエ" }, {"RYO", "リヨ" },
    //  わ行
    {"WA", "ワ" },   {"WI", "ウイ" },  {"WE", "ウエ" },  {"WO", "ヲ" },   {"DUMMY", "DUMMY" },
    //  ん行
    {"NN", "ン" },   {"DUMMY", "DUMMY" },    {"DUMMY", "DUMMY" },    {"DUMMY", "DUMMY" },    {"DUMMY", "DUMMY" },
    //  が行
    {"GA", "ガ" },  {"GI", "ギ" },  {"GU", "グ" },  {"GE", "ゲ" },  {"GO", "ゴ" },
    {"GYA", "ギヤ" },    {"GYI", "ギイ" },    {"GYU", "ギユ" },    {"GYE", "ギエ" },    {"GYO", "ギヨ" },
    //  ざ行
    {"ZA", "ザ" },  {"JI", "ジ" },  {"ZU", "ズ" },  {"ZE", "ゼ" },  {"ZO", "ゾ" },
    {"JA", "ジヤ" }, {"JYI", "ジイ" },    {"JU", "ジユ" }, {"JE", "ジエ" }, {"JO", "ジヨ" },
    //  だ行
    {"DA", "ダ" },  {"DI", "ヂ" },  {"DU", "ヅ" },  {"DE", "デ" },  {"DO", "ド" },
    {"DYA", "ヂヤ" },    {"DYI", "ヂイ" },    {"DYU", "ヂユ" },    {"DYE", "ヂエ" },    {"DYO", "ヂヨ" },
    {"DHA", "デヤ" },    {"DHI", "デイ" },    {"DHU", "デユ" },    {"DHE", "デエ" },    {"DHO", "デヨ" },
    //  ば行
    {"BA", "バ" },  {"BI", "ビ" },  {"BU", "ブ" },  {"BE", "ベ" },  {"BO", "ボ" },
    {"BYA", "ビヤ" },    {"BYI", "ビイ" },    {"BYU", "ビユ" },    {"BYE", "ビエ" },    {"BYO", "ビヨ" },
    //  ぱ行
    {"PA", "パ" },  {"PI", "ピ" },  {"PU", "プ" },  {"PE", "ペ" },  {"PO", "ポ" },
    {"PYA", "ピヤ" },    {"PYI", "ピイ" },    {"PYU", "ピユ" },    {"PYE", "ピエ" },    {"PYO", "ピヨ" },
    //  ヴ行
    {"VA", "ヴア" }, {"VI", "ヴイ" }, {"VU", "ビユ" }, {"VE", "ヴエ" }, {"VO", "ヴオ" }

};
/* 私だったら、最後に番兵を置いて、項目の数は計算機に計算させたいところだが、
気にしないことにする。
でも、半角カタカナは文字化け問題とかあるので使わないで欲しかった */


/***********************************************************************
* 配列の添字などの型には何故か unsigned long をプロ氏は使う            *
* プロ氏の環境ではそれがいいのかもしれないが、size_t とか              *
* 使った方が可搬性と可読性が増すと思う。                               *
* プロ氏は仕事でもこのようなプログラムを書いているのだろうか?         *
* というか、まったくの初心者向きなのだから、int を使う方がいいと思う   *
************************************************************************/
// ローマ字テーブルのデータ数(グローバル変数)
unsigned long RMojiTblMax = 35 * 5;

// ローマ字変換処理(今回は引数は使用しません)
int main(int argc, char* argv[])
{
/* 80 というマジックナンバーはマクロにした方がいいと思う */
char Buff[ 80 ]; // ローマ字変換結果格納メモリ
char work[ 80 ]; // ローマ字格納メモリ

/****************************************************************************
* プロ氏は「繰り返し」はすべて while(1){ ・・・ } 無限ループを使い、  *
* break; 等で脱出する。確かにこれひとつ覚えておけば繰り返しは全部     *
* 書けるが、for文とかwhile(条件)・・・ とかを覚えさせる方がいいと     *
* 思う。                                                              *
* というか、プロ氏は仕事でもこのスタイルで書いているのか?            *
* 「プロの世界ではコーディングルールというものがあって」云々言って    *
* いたが、どんなコーディングルールを使ってるのだろう?                *
*****************************************************************************/
// ローマ字入力をカナに変換する
// exitで終了
while( 1 )
{
memset( work, 0x00, sizeof( work ) );
memset( Buff, 0x00, sizeof( Buff ) );
// 文字列を取得する
printf( "ローマ字入力 : " );
fgets( work, sizeof( work ), stdin );
// 改行コードの除去
RemoveCRLF( work );
// 半角文字を全て大文字に変換する
Str2Upper( work );
// exitを入力すると処理終了
/***************************************************************
* strncmpのサイズを表す引数が4だと、EXITで始まる         *
* 長い文字列が入力されても終了する。5の方がいいかも      *
* しれない。片方がリテラルの場合はstrcmpを使う手もある。 *
***************************************************************/
if( !strncmp( work, "EXIT", 4 ) ){
break;
}
// ローマ字変換を行う
Romaji2Kana( work, Buff );
// 変換結果を表示する
printf( "ローマ字 : %s\nカナ変換後 : %s\n", work, Buff );

}
return 0;
}
// 改行コードの除去
void RemoveCRLF(
char *Buff
){
/***********************************************************************
* プロ氏は、文字列終端の'\0'もNULLマクロを使用している。         *
* 彼は「NULL はヌルポインタとヌル文字の2つの意味で使うのが      *
* デファクトスタンダードだ」と述べたが、本来NULLは null pointer  *
* constant を表すマクロである。                                  *
* このプログラムはC++としてコンパイルされるため NULL は 0 に     *
* 展開されるが、VisualStudio VC++では、純粋なc言語のソース      *
* を書きたい場合はソースファイル名に拡張子「.c」を明記するべき。 *
* 「.c」を付けてコンパイルすると NULL は ((void *)0) に展開      *
* されるので警告が出る。CとC++の非互換性のひとつである。         *
************************************************************************/

char *p = NULL;
/************************************************************************
* プロ氏は「(初心者にはわかりにくいだろうが)strchr関数は        *
* バッファサイズを指定する引数がないので使うべきではないのだ」    *
* と memchr 関数を使っているが、肝心のバッファサイズを取得する    *
* のに strlen 関数を使っている。これではバッファオーバーランの    *
* 虚弱性を解消したことにはならない。それを指摘したらプロ氏は      *
* 「fgets関数で取得した文字列は必ず終端がヌル文字になること       *
* が保証されている」と・・・それを仮定するならstrchrでかまわない。*
* 読みづらくなったのに虚弱性は解消されていない。また、虚弱性を    *
* 解消したつもりになっているのがかえって危険である。              *
*************************************************************************/
// 文字列からキャリッジリターンがないか?
p = (char *)memchr( Buff, '\r', strlen( Buff ) );
if( p != NULL ){
// あればNULLをセットする
*p = NULL; /* '\0' であるべき */
}
// 文字列から改行コードがないか?
p = (char *)memchr( Buff, '\n', strlen( Buff ) );
if( p != NULL ){
// あればNULLをセットする
*p = NULL; /* '\0' であるべき */
}

}
// ローマ字からカナに変換
int Romaji2Kana(
char *Src, // 変換する文字列
char *Buff // 変換結果を格納するメモリ
){
char work[ 3 + 1 ];
unsigned long Count = 0;
unsigned long Count2 = 0;

// 変換する文字列がない場合
if( strlen( Src ) <= 0 ){
// 何も処理しませんよ
return( -1 );
}
memset( work, 0, sizeof( work ) );
// 3文字分処理する
while( 1 )
{
// 3文字処理しても変換できない場合は処理終了
if( Count >= 3 ){
break;
}
work[ Count ] = Src[ Count ];
Count2 = 0;
// ローマ字テーブル分と一致するか検査するまで繰り返す
while( 1 ){
if( Count2 >= RMojiTblMax ){
break;
}
/**************************************************************
* だから、strlenでサイズを取得するならstrncmpのように   *
* サイズを引数とする関数を使ってもバッファオーバーラン  *
* の虚弱性は解消されないって。                          *
***************************************************************/
// 変換文字が一致したかな?
if( !strncmp(
work,
RMojiTbl[ Count2 ][ 0 ],
strlen( RMojiTbl[ Count2 ][ 0 ] )
)
){
// カナ変換文字をコピーする
memcpy( Buff,
RMojiTbl[ Count2 ][ 1 ],
strlen( RMojiTbl[ Count2 ][ 1 ] ) );
// 変換した文字列分を飛ばして残りの文字列を処理する
// 自分自身を呼び出す事をリカーシブルコール(再帰呼出)
// といいます。
Romaji2Kana( Src + Count + 1, Buff + strlen( Buff ) );
// 関数を終了させる
return( 0 );
}
Count2++;
}
Count++;
}
/** ここでリカーシブコールを使うか! **/
// 文字を1つ進めて残りの文字列を処理する
// 自分自身を呼び出す事をリカーシブルコール(再帰呼出)
// といいます。
Romaji2Kana( Src + 1, Buff );
return( 0 );

}
// 半角文字列を全て大文字に変換
void Str2Upper(
char *Buff
){
unsigned long Count = 0;
/**************************************************************************
* for文の方がすっきり書けると思う。unsigned longがやはり違和感。    *
*   繰り返しの中でstrlenを使ってるのもちょっと気になる。   *
***************************************************************************/
// 全ての文字列を処理する
while( 1 ){
// 文字列全て処理をすれば処理終了する
if( Count >= strlen( Buff ) ){
break;
}
/***************************************************************************
* シンタックスシュガーを使って Buff[Count] と書いた方が分かりやすい。*
***************************************************************************/
// 半角文字列を小文字から大文字に変換する
*( Buff + Count ) = toupper( *( Buff + Count ) );
Count++;
}
}

--------

実行例。gccでコンパイルするとやたらに警告(const char* を char*に変換、null pointer constant の意味以外で NULL を使った(この警告はgccで出たが、必ず警告してくれる保証はないらしい))が出た後、長すぎる入力でバグったようだ。fgets使ってるのに。デバグはしてない。する気がおきない。

----

ローマ字入力 : DUMMY
ローマ字 : DUMMY
カナ変換後 : ヅ
ローマ字入力 : asahi
ローマ字 : ASAHI
カナ変換後 : アサヒ
ローマ字入力 : assamu
ローマ字 : ASSAMU
カナ変換後 : アサム
ローマ字入力 : kyoukai
ローマ字 : KYOUKAI
カナ変換後 : キヨウカイ
ローマ字入力 : aaa
ローマ字 : AAA
カナ変換後 : アアア
ローマ字入力 : mmmyou
ローマ字 : MMMYOU
カナ変換後 : ミヨウ
ローマ字入力 : dummystring
ローマ字 : DUMMYSTRING
カナ変換後 : ヅリ
ローマ字入力 : hello world
ローマ字 : HELLO WORLD
カナ変換後 : ヘオヲ
ローマ字入力 : aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
ローマ字 : �AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAア�z �アアア�\�ア�WϿ�WϿ��x�ア�WϿtWϿ$���ア�アアアア����V�アアアアア��アアx���\�ア�ア��アア��ア�WϿ �����x��WϿアア�cϿアアϿ�cϿ�cϿ�cϿ�cϿdϿ5dϿjdϿudϿ�dϿ�dϿ�dϿ�dϿeϿ;eϿjeϿteϿ�jϿ�jϿkϿ3kϿfkϿ�klϿlϿ@lϿRlϿplϿ�lϿ�lϿ�lϿ�lϿ�lϿmϿmϿ1mϿCmϿ]mϿemϿtmϿ�mϿ�mϿ�mϿ�mϿ�mϿ�mϿnϿ^nϿ�nϿ�nϿ�nϿ�nϿア�oϿ*oϿDoϿXoϿzoϿ�oϿ�oϿ�oϿ�oϿアアア�w�!アア�ア����アアアアアア4�ア アアアアアアアアア�
       ア�ア
カナ変換後 : アアアアアアアアアアアアアアアアアアアアアアアアアアアAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAア�z �アアア�\�ア�WϿ�WϿ��x�ア�WϿtWϿ$���ア�アアアア����V�アアアアア��アアx���\�ア�ア��アア��ア�WϿ �����x��WϿアア�cϿアアϿ�cϿ�cϿ�cϿ�cϿdϿ5dϿjdϿudϿ�dϿ�dϿ�dϿ�dϿeϿ;eϿjeϿteϿ�jϿ�jϿkϿ3kϿfkϿ�klϿlϿ@lϿRlϿplϿ�lϿ�lϿ�lϿ�lϿ�lϿmϿmϿ1mϿCmϿ]mϿemϿtmϿ�mϿ�mϿ�mϿ�mϿ�mϿ�mϿnϿ^nϿ�nϿ�nϿ�nϿ�nϿア�oϿ*oϿDoϿXoϿzoϿ�oϿ�oϿ�oϿ�oϿアアア�w�!アア�ア����アアアアアア4�ア アア アアアアアアア�
                                        ア�ア
ローマ字入力 : ローマ字 : �AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAア�z �アアア�\�ア�WϿ�WϿ��x�ア�WϿtWϿ$���ア�アアアア����V�アアアアア��アアx���\�ア�ア��アア��ア�WϿ �����x��WϿアア�cϿアアϿ�cϿ�cϿ�cϿ�cϿdϿ5dϿjdϿudϿ�dϿ�dϿ�dϿ�dϿeϿ;eϿjeϿteϿ�jϿ�jϿkϿ3kϿfkϿ�klϿlϿ@lϿRlϿplϿ�lϿ�lϿ�lϿ�lϿ�lϿmϿmϿ1mϿCmϿ]mϿemϿtmϿ�mϿ�mϿ�mϿ�mϿ�mϿ�mϿnϿ^nϿ�nϿ�nϿ�nϿ�nϿア�oϿ*oϿDoϿXoϿzoϿ�oϿ�oϿ�oϿ�oϿアアア�w�!アア�ア����アアアアアア4�ア アア アアアアアアア�
                        ア�アア�アア�アア�アアアアアYϿア�oϿア�YϿアアアア%�z�<��g3a7�i686アアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアア
カナ変換後 : アアアアアアアアアアアアアアアアアアアアアアアアアアアAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAア�z �アアア�\�ア�WϿ�WϿ��x�ア�WϿtWϿ$���ア�アアアア����V�アアアアア��アアx���\�ア�ア��アア��ア�WϿ �����x��WϿアア�cϿアアϿ�cϿ�cϿ�cϿ�cϿdϿ5dϿjdϿudϿ�dϿ�dϿ�dϿ�dϿeϿ;eϿjeϿteϿ�jϿ�jϿkϿ3kϿfkϿ�klϿlϿ@lϿRlϿplϿ�lϿ�lϿ�lϿ�lϿ�lϿmϿmϿ1mϿCmϿ]mϿemϿtmϿ�mϿ�mϿ�mϿ�mϿ�mϿ�mϿnϿ^nϿ�nϿ�nϿ�nϿ�nϿア�oϿ*oϿDoϿXoϿzoϿ�oϿ�oϿ�oϿ�oϿアアア�w�!アア�ア����アアアアアア4�ア アア アアアアアアア�
                                        ア�アア�アア�アア�アアアアアYϿア�oϿア�YϿアアアア%�z�<��g3a7�i686アアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアアア
ローマ字入力 : ローマ字 : AAAAAAAAAAAAAA
カナ変換後 : アアアアアアアアアアアアアア
ローマ字入力 : exitttttttt
Segmentation fault (コアダンプ)

2016年5月 3日 (火)

C言語で GOTO(整数型式) マクロ作ってスパゲッティプログラムを作ってみた

ストレスがたまったので、C言語(_Bool 型やブロックの中途で変数の定義など使用)で、スパゲッティ・プログラムを作ってみた。
GOTO(整数型式) マクロでジャンプし、 LABEL(整定数)マクロ に制御が移る。両方共展開するとひとつの文となる。GOTOマクロの引数は定数でなくともよい。
ついでなので、basic風味にしてみた。

内容は「Hellow, World!」出力です。

spaghetti.c
----------

LABEL(10)   REM("'Hello, World!' を出力する")
LABEL(20)   REM("注意: これはC言語ソースです!")
LABEL(30)   REM("i: アルファベットの何番目の文字を出力するか示す")
LABEL(40)   INT i;
LABEL(50)   REM("j: 200番のFORの添字、および、負のとき'l'を1回だけ出力するフラグ")
LABEL(60)   INT j;
LABEL(70)   REM("return_label: 擬似サブルーチンの戻り先")
LABEL(80)   INT return_label = 0;
LABEL(90)   REM("uppercase: 出力する文字が大文字のとき真");
LABEL(100)  BOOL uppercase = true;
LABEL(110)  REM("curr_ch: 出力文字")
LABEL(120)  CHAR curr_ch = '\0';
LABEL(130)  REM("'H'を出力")
LABEL(140)  gosub(2000);
LABEL(150)  FOR_TO(i, 0, 127) // FOR i=0 TO 127: 127に特に意味はない
LABEL(160)      IF !i THENG(2030)
LABEL(170)      ELSE_IF i==4 THEN
LABEL(180)          REM("4番目の文字eを出力したならば")
LABEL(190)          REM(" 'l' を2回出力")
LABEL(200)          FOR_DOWNTO(j,2,1) // FOR j=2 TO 1 STEP -1
LABEL(210)              REM(" 'l' を出力する");
LABEL(220)              REM("j<0 のときは'l'を1回出力する擬似ルーチンの入り口")
LABEL(230)              gosub(2090); // 2090 はreturn_labelを弄らないので270番はOK
LABEL(240)          NEXT
LABEL(250)          IF j<0 THEN
LABEL(260)              REM("230番を入り口とした擬似サブルーチンとみなし、戻る")
LABEL(270)              GOTO( return_label );
LABEL(280)          END_IF
LABEL(290)      END_IF
LABEL(300)      GOTO(2500);
LABEL(310)  NEXT
LABEL(320)  STOP
LABEL(1000) REM("文字 i 番目を出力するサブルーチン")
LABEL(1010) curr_ch = 'a' + i;
LABEL(1020) IF uppercase THEN curr_ch = toupper( curr_ch ); END_IF
LABEL(1030) putchar( curr_ch );
LABEL(1040) RETURN
LABEL(2000) REM("'H'を出力するサブルーチン")
LABEL(2010) LET i = 7;
LABEL(2020) GOTO((curr_label/1000 -1)*1000); // 実は GOTO(1000)
LABEL(2030) REM("iを変更し、'e'を出力して 160 に戻る   ");
LABEL(2040) LET uppercase = false;
LABEL(2050) LET i = 4;
LABEL(2060) LET curr_ch = 'a' + i;
LABEL(2070) putchar( curr_ch );
LABEL(2080) GOTO(160);
LABEL(2090) REM("'l'を出力するサブルーチン")
LABEL(2100) LET uppercase = false;
LABEL(2110) LET i = 11;
LABEL(2120) GOTO(1000);
LABEL(2500) REM("ow, Wo を出力する")
LABEL(2510) REM("nest: 次のサブルーチンの深さ")
LABEL(2520) INT nest = 0;
LABEL(2530) REM("oW, Wo を出力する再帰的サブルーチン")
LABEL(2540) switch( nest ){
LABEL(2550) REM("このブロック内では、LABEL番号は実は直上のswitchに関連付けられるが")
LABEL(2560) REM("nest の値とかぶらない")
LABEL(2570) case 0:         putchar('o');
LABEL(2580)                 nest++;
LABEL(2590)                 gosub(2530); // 再帰的呼び出し
LABEL(2600)                 putchar('o');
LABEL(2610)                 REM("nest==0 はgosubで呼ばれないことが前提なのでGOTO脱出")
LABEL(2620)                 GOTO(3000);
LABEL(2630)                 REM("NOT REACHED")
LABEL(2640)                 break;
LABEL(2650) case 1:         putchar('w');
LABEL(2660)                 nest++;
LABEL(2670)                 gosub(2530); // 再帰的呼び出し
LABEL(2680)                 putchar('W');
LABEL(2690)                 break;
LABEL(2700) default:        putchar(',');
LABEL(2710)                 putchar(' ');
LABEL(2720)                 break;
LABEL(2730) }
LABEL(2740) RETURN
LABEL(3000) REM("rld! を出力")
LABEL(3010) INT offset = 4;
LABEL(3020) GOTO(3000 + 100*offset)
LABEL(3100) REM("'!'+改行を出力して停止する場所へGOTO") // LABEL 3000 + 1*100
LABEL(3110) puts("!");
LABEL(3120) REM("ラベル 150 FOR文の終値より充分大きな値をiに入れる")
LABEL(3130) LET i = 1000;
LABEL(3140) GOTO(310)
LABEL(3200) REM("'d' を出力")    // LABEL 3000 * 2*100
LABEL(3210) LET i = 3;
LABEL(3220) LET uppercase = false;
LABEL(3230) gosub(1000);
LABEL(3240) LET offset-- ;
LABEL(3250) GOTO(3020)
LABEL(3300) REM("'l' を出力") // LABEL 3000 +3*100
LABEL(3310) LET j = -1;
LABEL(3320) LET offset-- ;
LABEL(3330) LET return_label = 3020; // 擬似サブルーチンの戻り先
LABEL(3340) GOTO(230)
LABEL(3400) REM("'r' を出力") // LABEL 3000 + 4*100
LABEL(3410) LET i = 17;
LABEL(3420) LET uppercase = false;
LABEL(3430) gosub(1000);
LABEL(3440) LET offset-- ;
LABEL(3450) GOTO(3020)
LABEL(3500) REM("NOT REACHED")
LABEL(3510) STOP
LABEL(3520) END

----------

出力結果

----------

Hellow, World!
ラベル 320 で停止しました。

----------

メインプログラム

spaghetti-main.c
----------

#include "spaghetti.h"
#define LOG_FILE_NAME "labels.log"

static FILE *log;

int gosub( int curr_label );

int main( void ){
    if(!(log=fopen(LOG_FILE_NAME,"w"))){
        fputs(LOG_FILE_NAME  " が開けませんでした\n", stderr);
        return EXIT_FAILURE;
    }
    int ret = gosub( 0 );
    fclose(log);
    return ret;
}


int gosub( int curr_label ){
    // curr_label: 現在のラベル番号。LABELマクロ展開部分で更新される。
    while( true ){
        switch( curr_label ){
            // 無限ループの中にswitch文を入れることで
            // GOTO(整数式)を実現
LABEL(0)    // 先頭の文
            {

// 邪悪な include
#include "spaghetti.c"

            }           
            break;
default :   fprintf(stderr,
                "GOTO または gosub で存在しないラベルが参照されました。%d\n",
                curr_label );
            fclose(log);
            return EXIT_FAILURE;
            // not reached
            break;
        }
nextlabel:  // switch からの脱出先.
        ;
    }
    // not reached
    return EXIT_FAILURE;
}

ヘッダーファイル(マクロなどの定義)
spaghetti.h
----------

 

#ifndef SPAGHETTI_H
#define SPAGHETTI_H

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <ctype.h>

#define STOP    {printf("ラベル %d で停止しました。\n",curr_label); \
                    fclose(log); exit(EXIT_SUCCESS);}
#define END     {printf("ラベル %d でENDに到達しました。\n",curr_label); \
                    fclose(log); exit(EXIT_SUCCESS);}
#define RETURN  return EXIT_SUCCESS;
#define REM(X)
#define LET

#define INT  static int
#define BOOL static bool
#define CHAR static char

#define LABEL(label) {case (label): curr_label = (label); \
            fprintf(log, "LABEL %d\n", curr_label);}
#define GOTO(label) {curr_label=(label); goto nextlabel;}

#define IF if(
#define THEN ){
#define ELSE }else{
#define ELSE_IF } else if(
#define END_IF }
#define THENG(label) ){ GOTO(label)

#define FOR_TO( cnt, init, toval ) for((cnt)=(init);(cnt)<= (toval);(cnt)++ ){
#define FOR_DOWNTO( cnt, init, toval ) for((cnt)=(init);(cnt)>= (toval);(cnt)-- ){
#define NEXT }

#endif // SPAGHETTI_H

 

制御の流れ(通過したラベル番号)
labels.log
----------

 

LABEL 0
LABEL 10
LABEL 20
LABEL 30
LABEL 40
LABEL 50
LABEL 60
LABEL 70
LABEL 80
LABEL 90
LABEL 100
LABEL 110
LABEL 120
LABEL 130
LABEL 140
LABEL 2000
LABEL 2010
LABEL 2020
LABEL 1000
LABEL 1010
LABEL 1020
LABEL 1030
LABEL 1040
LABEL 150
LABEL 160
LABEL 2030
LABEL 2040
LABEL 2050
LABEL 2060
LABEL 2070
LABEL 2080
LABEL 160
LABEL 180
LABEL 190
LABEL 200
LABEL 210
LABEL 220
LABEL 230
LABEL 2090
LABEL 2100
LABEL 2110
LABEL 2120
LABEL 1000
LABEL 1010
LABEL 1020
LABEL 1030
LABEL 1040
LABEL 240
LABEL 210
LABEL 220
LABEL 230
LABEL 2090
LABEL 2100
LABEL 2110
LABEL 2120
LABEL 1000
LABEL 1010
LABEL 1020
LABEL 1030
LABEL 1040
LABEL 240
LABEL 250
LABEL 290
LABEL 300
LABEL 2500
LABEL 2510
LABEL 2520
LABEL 2530
LABEL 2540
LABEL 2580
LABEL 2590
LABEL 2530
LABEL 2540
LABEL 2660
LABEL 2670
LABEL 2530
LABEL 2540
LABEL 2710
LABEL 2720
LABEL 2740
LABEL 2680
LABEL 2690
LABEL 2740
LABEL 2600
LABEL 2610
LABEL 2620
LABEL 3000
LABEL 3010
LABEL 3020
LABEL 3400
LABEL 3410
LABEL 3420
LABEL 3430
LABEL 1000
LABEL 1010
LABEL 1020
LABEL 1030
LABEL 1040
LABEL 3440
LABEL 3450
LABEL 3020
LABEL 3300
LABEL 3310
LABEL 3320
LABEL 3330
LABEL 3340
LABEL 230
LABEL 2090
LABEL 2100
LABEL 2110
LABEL 2120
LABEL 1000
LABEL 1010
LABEL 1020
LABEL 1030
LABEL 1040
LABEL 240
LABEL 250
LABEL 260
LABEL 270
LABEL 3020
LABEL 3200
LABEL 3210
LABEL 3220
LABEL 3230
LABEL 1000
LABEL 1010
LABEL 1020
LABEL 1030
LABEL 1040
LABEL 3240
LABEL 3250
LABEL 3020
LABEL 3100
LABEL 3110
LABEL 3120
LABEL 3130
LABEL 3140
LABEL 310
LABEL 320