メンバ変数の保護(隠ぺい)について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
ゼノブレードクリア目前

メンバ変数の保護(隠ぺい)について

#1

投稿記事 by ゼノブレードクリア目前 » 15年前

以下のようなクラスを設計し、
ヘッダと.cppをコンパイルしたオブジェクトファイルを
提供するとします。
---- XXX.h ----

class XXX
{
public:
    void SetValue( int value );
    int  GetValue( void );
prvate:
    int m_Value;
}


---- XXX.cpp ----

void XXX::SetValue( int value )
{
    if ( value > 0)  m_Value = value;
}

int XXX::GetValue( void )
{
    return m_Value;
}
m_Value をprivateにしているのは
必ずメンバ関数を使ってほしいからです。
しかし、悪意をもったユーザーであれば
XXX.h の private を public に書き換えて
m_Value を直接扱うことができてしまうようです。

m_Valueの直接編集を防ぐ良い方法はないでしょうか?
(XXX.h は公開情報、XXX.cpp は非公開情報)

たかぎ

Re:メンバ変数の保護(隠ぺい)について

#2

投稿記事 by たかぎ » 15年前

> 悪意をもったユーザーであれば

このユーザーというのはプログラマのことですね?
だとすると、確実に直接編集を防ぐ方法は、ハード的にメモリアクセスを抑制するしかありません。

--- XXX.h ---

struct XXX;

void SetValue(XXX*, int);
int GetValue(const XXX*);
XXX* Create();
void Destroy(XXX*);

--- XXX.cpp ---

struct XXX
{
int value;
};

void SetValue(XXX* p, int value)
{
p->value = value;
}

int GetValue(const XXX* p)
{
return p->value;
}

XXX* Create()
{
return new XXX();
}

void Destroy(XXX* p)
{
delete p;
}

上のようにすれば多少はましですが、

XXX* p = &xxx;
*reinterpret_cast<int*>(p);

のようにすれば、悪意を持ったプログラマなら簡単に中身にアクセスできてしまいます。
あるいは、

--- XXX.cpp ---

#include <map>

struct XXX {};

namespace {
std::map<XXX, int> m;
}

XXX* Create()
{
XXX* p = new XXX;
m[p] = 0;
return p;
}

void SetValue(XXX* p, int value)
{
m[p] = value;
}

// 以下、省略

のようにすれば、直接編集はかなり困難になりますが、悪意と根性をもったプログラマならセクセスできてしまうでしょう。

ゼノブレードクリア目前

Re:メンバ変数の保護(隠ぺい)について

#3

投稿記事 by ゼノブレードクリア目前 » 15年前

ご回答ありがとうございます。

>このユーザーというのはプログラマのことですね?
はい。

他人に提供するコードなので、
new などの動的メモリ確保はできれば避けたいです。

ヘッダを書き換えると動かなくなると一番良かったのですが・・

dic

Re:メンバ変数の保護(隠ぺい)について

#4

投稿記事 by dic » 15年前

それではdllやlib形式でやりとりというのはどうでしょうか?

たかぎ

Re:メンバ変数の保護(隠ぺい)について

#5

投稿記事 by たかぎ » 15年前

> ヘッダを書き換えると動かなくなると一番良かったのですが・・

実行時にヘッダファイルを読み込むようにする以外ありませんね。

うしお

Re:メンバ変数の保護(隠ぺい)について

#6

投稿記事 by うしお » 15年前

はじめまして、

privateメンバの隠蔽には
1、PImpl イディオム(pointer to implementation idiom)と呼ばれる方法や、
2、インターフェースを用いる方法があります

前者1、は
//XXX_Impl.h
class XXX_Impl
{
public:
void SetValue( int value );
int GetValue( void );
prvate:
int m_Value;
}

//XXX_Impl.cpp
void XXX_Impl::SetValue( int value )
{
if ( value > 0) m_Value = value;
}

int XXX_Impl::GetValue( void )
{
return m_Value;
}

これのほかにもうひとつクラスを用意します
//XXX.h
class XXX_Impl;//宣言のみ
class XXX
{
public:
XXX();
~XXX();

void SetValue( int value );
int GetValue( void );
prvate:
XXX_Impl* pImpl;
}

//XXX.cpp
#include "XXX_Impl.h"

XXX::XXX(){
  pImpl = new XXX_Impl();
}
XXX::~XXX(){
delete pImpl;
pImpl = 0;
}
XXX::SetValue( int value ){
pImpl->SetValue(value);//呼び出すだけ
}
int XXX::GetValue( void ){
return pImpl->GetValue();
}

こうしておけば、使い側は、XXXのヘッダのみを見てクラスを使うことが可能となり、
より実装を隠蔽できます
たとえば、

#include "XXX.h"
int main(){
XXX TextX;
TextX.SetValue(30);
TextX.GetValue();
return 0;
}
この場合、XXX_Implのint m_Value;は影も形もありません
省略してしまいましたが、
XXX(const XXX&);とoperator=(const XXX&);
をprivateにしてコピーコンストラクタを禁止にするなどして、
動的に確保したメモリのアクセス違反などを避ける工夫が必要となる場合も
あるので少し注意が必要です。

また、XXX_Implのメソッドをすべてinlineにすればオーバーヘッドを軽減することもできます
2は分けて投稿します

うしお

Re:メンバ変数の保護(隠ぺい)について

#7

投稿記事 by うしお » 15年前

2、インターフェースを用いる方法

こちらは継承を利用する方法です
最初に抽象インターフェースを用意します

//XXX.h
class XXX{
public:
virtual ~XXX(){}//deleteのときのnewとの整合性を保つ

static XXX* Create();//裏でnewするメソッド
virtual void SetValue( int value ) = 0;//純仮想関数
virtual int GetValue( void ) = 0;
};

次にXXXを派生させてそれぞれのメソッドを実装します

//XXX_Derived.h
#include "XXX.h"
class XXX_Derived : public XXX
{
public:
XXX_Derived():m_Value(0){
}
~XXX_Derived(){}
void SetValue( int value ){
if ( value > 0) m_Value = value;
}
int GetValue( void ){
return Value;
}
private:
int m_Value;
}

Createスタティックメソッドを実装します
//XXX.cpp
#include "XXX_Derived.h"
XXX* XXX::Create(){
return new XXX_Derived();
}

このようにすることで、
XXXを使う側はXXX.hを見るだけで使えるのでprivateメンバを隠蔽できます

たとえば、
#include "XXX.h"
int main(){
XXX* p = XXX::Create();

p->SetValue(30);//実行時ポリモーフィズムという機能で実態に応じた処理がなされる
p->GetValue();

delete p;//忘れないように注意
p = 0;
return 0;
}
こちらの方法でもm_Valueはどこにも見当たらないにもかかわらず、クラスを使うことができます

こちらも実装方法がいくつもあるので状況&好みで使い分けると良いでしょう。

2つの方法どちらでも、
・privateメンバを隠蔽する
・使うときに余計な情報を知らなくてすむ
・コンパイル依存性を減らせる
等メリットが得られます

しかし、
・メソッドのインターフェース修正に弱い(修正箇所が多いため)
・ファイルが増える
・若干めんどくさい
・若干のオーバーヘッドがある
などのデメリットもあるために、

1、独立した機能を持つクラスである
2、メソッドに変更はあまりない

というクラスにのみ適用すべきであることがわかります
ライブラリなどには比較的親和性がいいと思います

適材適所だと思います
どれも当てはめればいいというわけではなく、単にprivateのほうが柔軟に対応できる場合も
多いと思うので、乱用には注意すると良いと思います。

蛇足ですが、
deleteを忘れないために、
shared_ptrやunique_ptrなどのスマートポインタテンプレートクラスを利用すると
メモリリークのリスクを減らすことが可能です

※ザックリ書いたので、ソースに;がなかったり足りない部分もあるので、
 あくまでも考え方として読んでいただければと思います

ゼノブレードクリア目前で飽き気味

Re:メンバ変数の保護(隠ぺい)について

#8

投稿記事 by ゼノブレードクリア目前で飽き気味 » 15年前

皆さんアドバイスありがとうございました。

私が携わっている組み込み系ハード用OSの文化として
ライブラリが動的メモリ確保を行うことはあまり
好まれない傾向にあります。

そのため教えていただいた方法は
採用できませんでしたが一般論としてとても勉強になりました。

今回は、メンバ変数が悪意をもって任意に書き換えられるのは
防げないものとして、そのメンバ変数を扱うメンバ関数において
使用前に不正チェックを行うことにしました。ダサいですけど・・

ありがとうございました。

閉鎖

“C言語何でも質問掲示板” へ戻る