ページ 11

グローバル変数の危険性を減らすために

Posted: 2012年2月02日(木) 23:45
by Shota
以下のようなマクロを作りました。

コード:

; gaccess.h-------------------------------------------------------
#ifndef INCLUDED_GACCESS_H
#define INCLUDED_GACCESS_H

#include <stdio.h>

#ifdef GLOBAL_INSTANCE_DEBUG
#define CREATE_GLOBAL(TYPE, VALUE) MAKE_GLOBAL_INSTANCE_DEBUG(TYPE, VALUE)
#else
#define CREATE_GLOBAL(TYPE, VALUE) MAKE_GLOBAL_INSTANCE(TYPE, VALUE)
#endif

#define MAKE_GLOBAL_INSTANCE_DEBUG(TYPE, VALUE)\
	static TYPE *VALUE(int line){\
		static TYPE VALUE##_data;\
		fprintf(stderr ,"use global instanse '%s' in %d.\n", #VALUE, line);\
		return &VALUE##_data;\
	}
// end define
#define MAKE_GLOBAL_INSTANCE(TYPE, VALUE)\
	static TYPE *VALUE(int line){\
		static TYPE VALUE##_data;\
		return &VALUE##_data;\
	}
// end define

#define GLOBAL_ACCESS(VALUE) (*VALUE(__LINE__))

#endif
; end gaccess.h-------------------------------------------------------

コード:

こんな感じで使用します。
; main.c----------------------------------------------------------------
#include <stdio.h>

#define GLOBAL_INSTANCE_DEBUG
#include "gaccess.h"

CREATE_GLOBAL(int, x)
#define x GLOBAL_ACCESS(x)

CREATE_GLOBAL(int *, p)
#define p GLOBAL_ACCESS(p)

int main(){
	x = 5;
	p = &x;
	printf("%p %d", p, *p);
	return 0;
}
CREATE_GLOBALで定義したx,pは元は関数なのですが、変数に見立てて使用しています。
関数なので、不意に意図しないところで値が書き換えられたとしても、最後に呼ばれた行を出力することができるのでデバッグがしやすいはずです。

グローバル変数は使いたくなかったのですが、使わないとコードがとんでもなくなりそうな気がしたので、このようなマクロを定義してみたのですが、実用性はあると思いますか?
このマクロだと普通の変数と違ってこんなことができない、だとか、何か弊害があれば教えてください。


codeタグを追加しました。詳しくはフォーラムルールをご覧ください。 by softya(ソフト屋)

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月02日(木) 23:52
by Shota
すみませんcodeで囲い忘れました。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:10
by Poco
このマクロだと普通の変数と違ってこんなことができない、だとか、何か弊害があれば教えてください。
グローバル変数の配列はどう定義すればいいのでしょうか?

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:11
by softya(ソフト屋)
初期値も問題ですね。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:15
by ISLe
わたしがグローバル変数の意味を分かっていないのかもしれませんが。
別のfoo.cというソースファイルで定義した関数からxの値を書き換えたいときはどうしたら良いのでしょう。

グローバル変数というのは、staticの付いてない外部変数だと思ってます。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:16
by Shota
ポインタ型で定義して動的確保をすれば配列として機能します。
静的変数に動的確保ってのも変な感じですが。

コード:

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

#define GLOBAL_INSTANCE_DEBUG
#include "./lexeropt_2/gaccess.h"

CREATE_GLOBAL(int *, ary)
#define ary GLOBAL_ACCESS(ary)


int main(){
	ary = malloc(sizeof(int)*2);
	ary[0] = 1;
	ary[1] = 2;
	printf("%d %d", ary[0], ary[1]);
	free(ary);
	return 0;
}
freeすると確保されたメモリ領域自体は解放されますが、aryはstaticなのでポインタ変数のメモリ領域はプログラム終了まで残るはずです。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:20
by Shota
>>ソフト屋さん
初期値用も指定できるマクロを作れば実装できなくはないですが、確かにかなりごちゃごちゃしますね。

#define MAKE_GLOBAL_INSTANCE_INIT(TYPE, VALUE, INIT)\
static TYPE *VALUE(int line){\
static TYPE VALUE##_data = INIT;\
return &VALUE##_data;\
}
// end define

>>IsLeさん
一応staticを外せば外部からも参照できます(extern、プロトタイプ宣言をするマクロでもつくり)
ただ、関数名なのでstaticを外した場合に他のファイルで他の変数として同名の変数が定義されている場合はリンクエラーになると思いますので注意が必要です。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:26
by ISLe
検討してみましたけど、わたしはグローバル変数を使わないので実用性があるかどうか自体を判断できないですね。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:36
by softya(ソフト屋)
私なら安全性にこだわるならsetter/getter関数を使うと思います。その方が直感的ですし。
あと変数名の付け方がマズイと思わぬ名前の置き換えで困るコンパイルエラーが出そうです。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:45
by Shota
最初は

コード:

static int *getInstance_x(){
 static int data;
 return &data;
 }
 void set_x(int data){
 *getInstance_x() = data;
 }
 int get_x(){
 return *getInstance_x();
 }
こんな感じできちんと書いていたのですが、やはりスタンダードに書くべきですかね。
ただこれだと1つのグローバル変数のために名前空間を3つも汚すことになるんですよね。

ちなみに、どのような名前だとコンパイルエラーが起きそうですか?

#define SWAP(x, y) do { int temp = x; x = y; y = temp; } while(0)
このようなマクロの場合だとxかyにtempを指定するとエラーになるのは分かるのですが。。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 00:57
by Poco
Shota さんが書きました: ちなみに、どのような名前だとコンパイルエラーが起きそうですか?
グローバル変数と同名の変数をローカル変数として定義した時は起きますね。
#お行儀が悪いのをコンパイルエラーとして弾いていると見るべきか、このマクロ使わなければコンパイル通るのに、と見るべきか。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 01:04
by Shota
あ…確かにグローバル空間にある関数とローカル空間にある変数が衝突しますね。
これは気づいていませんでした。ありがとうございます。

具体的な問題点はこれくらいでしょうか。

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 01:11
by h2so5
ぽこ さんが書きました: グローバル変数と同名の変数をローカル変数として定義した時は起きますね。
#お行儀が悪いのをコンパイルエラーとして弾いていると見るべきか、このマクロ使わなければコンパイル通るのに、と見るべきか。
スコープが違うので変数名が同じでもエラーは出ませんよ。
http://ideone.com/Y9XSI


失礼。思いっきり勘違いしてました(-_-;)

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 01:29
by Poco
先ほどのコメントと同じような内容ですが、
構造体のメンバとも被れませんね。

コード:

struct A {
    int * p;
};

struct A a;

CREATE_GLOBAL(int *, p)
#define p GLOBAL_ACCESS(p)

int main(){
    a.p = 4
    return 0;
}

Re: グローバル変数の危険性を減らすために

Posted: 2012年2月03日(金) 01:42
by Shota
構造体メンバとの重複、おそらくこれが一番痛いですね…。

グローバル変数は他と被らない長い変数名にする、だとか、接頭語としてGlobalをつけるだとかしないと実用性はなさそうですね。
その条件を満たした上でなら、この少し改良したマクロが使えそうです。

コード:

#ifndef INCLUDED_GACCESS_H
#define INCLUDED_GACCESS_H

#include <stdio.h>

#ifdef GLOBAL_INSTANCE_DEBUG
#define CREATE_GLOBAL(TYPE, VALUE) MAKE_GLOBAL_INSTANCE_DEBUG(TYPE, VALUE)
#define GLOBAL_ACCESS(VALUE) (*VALUE(__LINE__))
#else
#define CREATE_GLOBAL(TYPE, VALUE) TYPE VALUE
#define GLOBAL_ACCESS(VALUE) VALUE
#endif

#define MAKE_GLOBAL_INSTANCE_DEBUG(TYPE, VALUE)\
	static TYPE *VALUE(int line){\
		static TYPE VALUE##_data;\
		fprintf(stderr ,"use global instanse '%s' in %d.\n", #VALUE, line);\
		return &VALUE##_data;\
	}
// end define
#endif
GLOBAL_INSTANCE_DEBUGが定義されているときは関数として、定義されていないときは通常通りのグローバル変数として定義する仕様になっています。
グローバル変数を使用していてどこで書き換えられたかチェックしたいときのみGLOBAL_INSTANCE_DEBUGを定義する…などの使い方が。

まぁ、これでもやはり上記レスに挙がってる問題は解決できないのですが…。
そもそもGLOBAL_INSTANCE_DEBUGを定義しているときとしていないときで挙動が変わったら大問題ですよねorz