【C言語】ポインタとメモリの話【ヒープ/スタック】

| 0件のコメント

ポインタ自体はメモリのアドレスを入れている変数ですが、単純かつ奥が深い機能です。
他の言語では意識しないことまで気を使わないといけません。単純なミスとして, strcpyを使わないで, loop内で繰り返しポインタへの代入を行うと代入元のアドレスが置き換わってしまって…ということがあります。
ポインタの動作にはC言語のメモリ管理構造 (特にスタックとヒープの構造)がイメージできるかがポイントと思います。

関数とポインタ

1. データをポインタで関数に渡す
ポインタを使った簡単な方法のひとつで, 関数内でデータの値を変更するパターンです。

void swapNum(int *a,int *b){
	int tmp;
	tmp = *a;
	*a = *b;
	*b = tmp;
};

int main(void){
	int a = 1;
	int b = 2;
	printf("a:%d , b:%d\n",a,b );// 1,2
	swapNum(&a,&b);
	printf("a:%d , b:%d\n",a,b );// 2,1

    return 0;
};

2. 関数ポインタで関数呼び出し
関数ポインタの基本的役割は, 動的に読み出す先の関数を切り替えることです。

int (*f_ptr)(int);

int doubleNum(int n){
	return n*2;
}

int squareNum(int n){
	return n*n;
}

int main(void){
       int num = 5;
       f_ptr = doubleNum;
       printf("num -> %d double -> %d\n",num,f_ptr(num));// 10
       f_ptr = squareNum;
       printf("num -> %d square -> %d\n",num,f_ptr(num));// 25

       return 0;
}

このデメリットはパイプラインによる予測分岐の高速化が使えないことらしいです。

3. 関数ポインタを関数から返す
上記のプログラムを改造してより抽象度をあげた記述もできます。

typedef int (*calc)(int);

int doubleNum(int n){
	return n*2;
}

int squareNum(int n){
	return n*n;
}

calc select(char opcode){
	switch(opcode){
		case 'x': return doubleNum;
		case '^': return squareNum;
		default : return 0;
	}
};

int opelation(char dummy){
	int num = 5;
	calc op = select(dummy);
	return op(num);
}

int main(void){
	int ret = opelation('x');
	int ret2 = opelation('^');
	printf("ret -> %d , ret2 -> %d\n", ret,ret2);// 10,25

  return 0;
}

構造体とポインタ

構造体とポインタを上手く活用できるかがプログラムの拡張性に影響します。

構造体とポインタでは参照の仕方が異なります。
typedefで作成したPerson型の_person構造体をPerson personでインスタンス化した場合, 直接メンバ(ドット)参照でアクセスします。
一方, ポインタで宣言した場合は間接メンバ参照でアクセスできます。

typedef struct  _person{
	char *name;
	char *gender;
	unsigned int age;
}Person;
 
int main(void){
	// 直接メンバ(ドット)参照
	Person person;
	person.age = 10;
	printf("person.age :%d\n",person.age);

	// 間接メンバ参照
	Person *person2;
	person2 = (Person*)malloc(sizeof(Person));
	person2->age = 20;
	printf("person->age :%d\n",person2->age);
	free(person2);

  return 0;
}

このようなデータ構造では, 以下のように初期化と解放の関数を作ると良いかもしれません。

typedef struct  _person{
	char *name;
	char *gender;
	unsigned int age;
}Person;

void initalized(Person *person, const char* fn,const char* ln,unsigned int age){
	person->name = (char*)malloc(strlen(fn) + 1);
	strcpy(person->name, fn);
        person->gender = (char*)malloc(strlen(ln) + 1);
	strcpy(person->gender, ln);
	person->age = age;
}

void dealloc(Person *person){
	free(person->name);
	free(person->gender);
}

personLifeCycle関数をつくりその中で Person *personを宣言した場合ですが, この時 initalized関数で割り当てた person構造体内のポインタ変数は前述の dealloc関数で解放されます。
ここで注意しておきたいのは, mallocで確保したperson自体もfreeで解放しなければならない点です。

void personLifeCycle(void){
   Person *person;// 構造体へのポインタ
   person = (Person*)malloc(sizeof(Person));// 構造体のメモリ確保
   initalized(person, "mameroot","man",20);// 初期化

   printf("person->name :%s\n",person->name);
   printf("person->age :%d\n",person->age);

   dealloc(person);// 初期化時のメモリ解放
   free(person);// 構造体自体の解放
}
 
int main(void){
    personLifeCycle();
    return 0;
}

解放のタイミングによっては, dangling pointerを招いたりポインタとメモリの関係は手を焼くことが多いですが, この辺を上手く操れると上達した感がでてきます。

ダブルポインタ

コマンドのオプション処理の実装はダブルポインタの典型的な使い方をしているので勉強になります。
特にGNU-Coreutilsはオススメです。

$ git clone git://git.sv.gnu.org/coreutils.git

関連の話題は, 【GNU】unameソースコードリーディング【coreutils】を参照ください。

boehm GC

boehm GCは, ガベージコレクション (GC)が実装されていないC言語でGCを実現してくれるライブラリです。

$ yum install gc-devel
$ apt-get install libgc-dev

有名なプロジェクトでも使われているみたいなので使ってみたいです。

おわりに

C言語は一般的にオブジェクト指向型言語ではありません。
言語として積極的に実装されていないのでオブジェクト指向型言語ではないのは確かにそうです。
しかしオブジェクト指向自体は考え方なので, オブジェクト指向的実装は可能です。

例えば, opaqueポインタを使って外部データ型を隠蔽することで汎用性を持たせることができます。
ただ継承を使いたい場合, Cではやや書きづらいのでC++の方が簡単なのはあると思います。
この場合, C++からCソースをリンクする場合は #ifdef cplusplus extern “C” をCソースに追加します。
というのも, G++コンパイラではCソースに対して #define cplusplusが付与されるようですが関数オーバーライド機能との内部互換的な話で必要のようです、

コメントを残す

必須欄は * がついています