Hatena::ブログ(Diary)

滴了庵日録 このページをアンテナに追加 RSSフィード

2012/11/06(Tue)

C#のプロパティ

いまさらだけども、C#のプロパティについて。要するに、外見はメンバ変数、内実はメソッドということですね。C++Javaにはない便利な文法です。getter/setterを変数に見せかける糖衣構文といったところでしょうか。

namespace PropertyTest
{
    class Hoge
    {
        public int Piyo
        {
            get { return piyo; }
            set { piyo = value; } // valueはキーワード
        }
        private int piyo;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Hoge a = new Hoge();
            a.Piyo = 101;
            System.Console.WriteLine( "a.Piyo = " + a.Piyo );
            
            while (true) ;
        }
    }
}

さらにデリゲートを使って、下記のようなことをすることもできますが、まあ、こんなのはせずに済むにこしたことはないでしょうね。

namespace DelegateTest
{
    delegate int GetIntDelegate();
    delegate void SetIntDelegate(int val);
    
    class Hoge
    {
        public int Piyo
        {
            get { return piyo; }
            set { piyo = value; }
        }
        private int piyo;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Hoge a = new Hoge();

            GetIntDelegate GetPiyo = delegate
            {
                return a.Piyo;
            };
            SetIntDelegate SetPiyo = delegate(int val)
            {
                a.Piyo = val;
            };

            SetPiyo(666);
            System.Console.WriteLine( "a.Piyo = " + GetPiyo() );

            while (true) ;
        }
    }
}

2012/11/05(Mon)

VC++の関数のアドレス

C言語で、関数のアドレスはポインタとして取得できるはずだが、Visual C++の場合、それは実際にはスタブでそこからジャンプ命令で関数の本体のアドレスに飛ぶ。引数のある関数の場合、その引数のアドレスの前が戻り先のアドレスであるはずなので、下記のようなコードで、関数の呼び出し元の関数を推定できる。アドレスが分かったら、[デバッグ]>[ウィンドウ]>[逆アセンブル]で調べてみると良い。

#include<stdio.h>
typedef unsigned int UINT;
typedef unsigned char BYTE;

void hoge(int a);

void piyo(void)
{
    printf("piyo\n");
    hoge(1);
}
void fuga(void)
{
    printf("fuga\n");
    hoge(2);
}

void hoge(int a)
{
    // 引数のアドレスの前にこの関数からの戻り先のアドレスが格納されているはず
    UINT p = *(UINT*)(((BYTE*)&a)-4);
    printf("hoge is called from %08X\n", p);
}

int main(void)
{
    // これが関数のアドレスのはずだが、実はスタブで…
    UINT ptr_piyo = (UINT)(void*)piyo;
    // この相対アドレスにジャンプする
    UINT offset_piyo = *(UINT*)((BYTE*)ptr_piyo + 1);
    // なので、関数の本体のアドレスはここ
    UINT real_piyo = (UINT)((BYTE*)ptr_piyo + offset_piyo + 5);  
    printf("piyo %08X\n", real_piyo);

    UINT ptr_fuga = (UINT)(void*)fuga;
    UINT offset_fuga = *(UINT*)((BYTE*)ptr_fuga + 1);
    UINT real_fuga = (UINT)((BYTE*)ptr_fuga + offset_fuga + 5);
    printf("fuga %08X\n", real_fuga);

    piyo();
    fuga();
    
    getchar();
    return 0;
}

なんだかよく分からないstd::for_each

いまさらながら、C++のstd::vectorの使い方を調べるなど。動的配列ということは分かったし、イテレータの使い方も分かったけど、std::for_eachを使った回し方はなんかよく分からない。ここまでトリッキーなことをして、どういうご利益があるのかな?

#include <stdio.h>
#include <vector>
#include <algorithm>

// intのベクター(動的配列)
typedef std::vector<int> VECTOR;

// 関数オブジェクト
class PrintInt
{
public:
    // ()演算子の実装
    void operator()(int num)
    {
        printf("PrintInt %d\n",num);
    }
};

int main(void)
{
    VECTOR v;
    for(int i = 0; i < 10; ++i )
    {
        // ベクターの末尾に要素を追加する
        v.push_back( i * 10 );
    }

    // ふつうに配列の添え字を回す
    for(int i = 0; i < v.size(); i++ )
    {
        printf("v[%d] = %d\n", i, v[i]);
    }

    // イテレータで回す
    for(VECTOR::iterator it=v.begin(); it!=v.end(); it++)
    {
        printf("*it = %d, it++\n", *it);
    }

    // std::for_eachで回す ...なんだかよく分からない
    std::for_each(v.begin(), v.end(), PrintInt());

    getchar();
    return 0;
}

通りすがり通りすがり 2012/11/17 14:06 配列の書き方[]を使ってしまっていると、コンテナを変えた瞬間に膨大なソース変更になります。
イテレータはそれをラップしてくれます。
今回の例なら、for_eachで書いておくと、vector以外のコンテナに変えたときもコードを変えなくていいとか。

lichenglicheng 2012/11/21 23:36 コメントありがとうございます。
イテレータの意義はまあ分かるような気はします。std::for_eachはどうも分からないですね。処理の中身を関数で渡してるので、すげ替えができそうな気はしますが、どんなところで効果的なのか...

2012/11/04(Sun)

実行中のメソッドをログ(C#)

実行時にメソッド名やコールスタックなどのメタデータを取得する仕組みをreflectionと言うそうですが、C#だと、System.Reflectionクラスがその機能を提供してくれます。下記は実行中のメソッドを表示するサンプルプログラム

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Reflection;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Hoge();
            while (true) { ;}
        }

        static void Hoge()
        {
            // これを必要な箇所にコピペしまくっても良いけど…
            MethodBase mb = MethodInfo.GetCurrentMethod();
            Debug.WriteLine("[Debug]" + mb.DeclaringType.Name + 
                                  "." + mb.Name + "を実行中");
            // こっちのほうが綺麗で便利。
            Logger.Log();
        }
    }

    class Logger
    {
        public static void Log()
        {
            StackTrace st = new StackTrace(true);
            StackFrame sf1 = st.GetFrame(1);
            MethodBase mb1 = sf1.GetMethod();
            StackFrame sf2 = st.GetFrame(2);
            MethodBase mb2 = sf2.GetMethod();
            Debug.WriteLine("[Log]" 
              + Path.GetFileName(sf2.GetFileName()) + ":" 
              + sf2.GetFileLineNumber() + "行目 "
              + mb2.DeclaringType.Name + "." + mb2.Name + "から呼ばれた");
            Debug.WriteLine("[Log]"
              + Path.GetFileName(sf1.GetFileName()) + ":"
              + sf1.GetFileLineNumber() + "行目 "
              + mb1.DeclaringType.Name + "." + mb1.Name + "を実行中");

            // ※ デバッグ情報が無く、行番号を取得できないなら
            // string address = sf2.GetMethod().ToString() 
            //      + "@ IL_" + sf2.GetILOffset().ToString("X4");
            // Debug.WriteLine(address);
        }
    }
}

ファイル名と行番号の取得は、デバッグ情報の無いリリースビルドに対しては無効なので、その場合は、上記のGetILOffsetを用いて、MSIL(.NETの中間言語)でのオフセットアドレスを取得し、MSIL逆アセンブラ(Ildasm.exe)に実行ファイルをドロップして、表示されたアドレスの1行前を見る。Ildasm.exeは、Program Files\Microsoft SDKs\Windows\v7.0A\Bin とかにあるはず。

2012/11/03(Sat)

default(Type)

C#は静的型づけの言語なので、varもあくまで型推論で静的な型が決定するものです。

var hoge = new Hoge();
hoge = new Piyo();    // ←エラーになる

なので、varの宣言時にnullを代入しようとすると、型が推論できないのでエラーになります。そういうときはdefaultキーワードを用います。

var hoge = null;   // ←エラーになる
var piyo = default(Piyo);

defaultキーワードはまた、ジェネリクスの定義内でも用いられます。default(Type)は、数値型の場合は0で、 参照型の場合はnull、構造体の場合は全てのメンバが0ないしnullになります。

2012/11/02(Fri)

関数の乗っ取り

hoge() を piyo() で乗っ取る例。(WindowsVC++にて)

#include <stdio.h>
#include <windows.h>

void hoge(void)
{
    printf("hogehoge\n");
}

unsigned int original_hoge;

void piyo(void)
{
    printf("piyopiyo\n");

    // 本来のhogeを呼び出す
    ((void(*)())original_hoge)();
}

int main(void)
{
    // hogeのスタブのアドレス
    unsigned int stub_hoge = (unsigned int)(void*)hoge;
    // ジャンプ先へのオフセット値
    unsigned int* offset = (unsigned int*)((unsigned char*)stub_hoge+1);
    // 本来のhogeのアドレス
    original_hoge = stub_hoge + *offset + 5;

    // piyoのスタブのアドレス
    unsigned int stub_piyo = (unsigned int)(void*)piyo;


    // 仮想アドレスのアクセス保護解除
    DWORD dwOldProtect;
    VirtualProtect(offset, sizeof(offset), PAGE_EXECUTE_READWRITE, &dwOldProtect);
    // hogeのスタブの値をpiyoのスタブへジャンプするように書き換え
    *offset = stub_piyo - (stub_hoge+5);
    // 仮想アドレスのアクセス保護を元に戻す
    VirtualProtect(offset, sizeof(offset), dwOldProtect,&dwOldProtect);

    // hogeを呼んでみる
    hoge();

    getchar();
    return 0;
}

2012/11/01(Thu)

疎行列

疎行列(ほとんどの要素がゼロであるスカスカな行列)は、2次元配列で実装するとメモリの無駄遣いになるので、サイズが大きいなら連想配列で実装したほうが良い。C++でのやっつけな実装例。

#include<stdio.h>
#include<map>

// 疎行列
class SparseMatrix
{
public:
    typedef std::pair<int,int> Index;    // (i,j)の組
    typedef std::map<Index, int> Map;    // (i,j)-> の連想配列
    
    // (i,j)の値を取得する
    int get(int i, int j)
    {
        // (i,j)を連想配列から探す
        Map::iterator it = m_map.find( Index(i,j) );
        if( it == m_map.end() ){
            return 0;            // 見つからなければゼロ
        }else{
            return it-&gt;second;    // 見つかったら対応する k を返す
        }
    }

    // (i,j)の値 k を設定する
    void set(int i, int j, int k)
    {
        m_map[ Index(i,j) ] = k;
    }

    // 行列をゼロクリアする
    void clear(void)
    {
        m_map.clear();
    }

    // ゼロでない (i,j)->k をリスト表示する
    void dump(void)
    {
        printf("Dump:\n");
        Map::iterator it = m_map.begin();
        while( it != m_map.end() )
        {
            int i = it->first.first;
            int j = it->first.second;
            int k = it->second;
            printf("(%d,%d)->%d\n",i,j,k);
            it++;
        }
    }

private:
    Map m_map;    // 連想配列 (i,j)->k
};

// テストプログラム
int main(void)
{
    SparseMatrix A;
    A.clear();
    A.set(1,1,1);
    A.set(10000,0,100);

    printf("A(0,0) = %d\n",A.get(0,0));
    printf("A(1,1) = %d\n",A.get(1,1));

    A.dump();

    char s = getchar();
    return 0;
}