このプログラムのポインタ演算について質問があります

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
sadora3
記事: 175
登録日時: 7年前

このプログラムのポインタ演算について質問があります

#1

投稿記事 by sadora3 » 4年前

このプログラムの31行目と50行目のことなのですが、なぜポインタ変数(ポインタ変数というかポインタ構造体?)に+6すると次のポインタ変数を表し、-6すると前のポインタ変数を表しているのでしょう?なぜ+1じゃだめなのでしょうか?

言語:C
OS:Windows7
コンパイラ:VisualStudio2010

コード:

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

int nNum = 1;

struct CharactorData{
	int Hp;
	int Mp;
	struct CharactorData *next;
};

void InputData(CharactorData*);
void OutputData(const CharactorData*, int);

void main(){
	CharactorData* pRoot = NULL;
	CharactorData* pTemp = NULL;
	int nSelect;

	while(1){
		printf("=====1:追加 2:表示 3:終了=====\n");
		printf("何を選びますか:");
		scanf("%d", &nSelect);
		if(nSelect == 1){
			CharactorData* pPlayer;
			pPlayer = (CharactorData*)malloc(sizeof(CharactorData));
			InputData(pPlayer);
			pPlayer->next = NULL;
			if(nNum == 1){	pRoot = pPlayer;	}
			else{	(pPlayer - 6)->next = pPlayer;	}
			nNum++;
		}
		else if(nSelect == 2){
			if(nNum > 1){
				int i = 1;
				pTemp = pRoot;
				while(pTemp != NULL){
					OutputData(pTemp, i);
					pTemp = pTemp->next;
					i++;
				}
				printf("\n");
			}else{
				printf("表示させるべきデータがありません\n");
			}
		}else if(nSelect == 3){
			pTemp = pRoot;
			for(int i = 0; i < nNum - 1; i++){
				free(pTemp + i * 6);
			}
			break;
		}else{
			printf("追加か表示か終了以外は出来ません\n");
		}
	}

	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
}

void InputData(CharactorData* C){
	printf("\n");
	printf("%d人目のHPを入力してください:", nNum);
	scanf("%d", &C->Hp);
	printf("%d人目のMPを入力してください:", nNum);
	scanf("%d", &C->Mp);
	printf("\n");
}

void OutputData(const CharactorData* C, int i){
	printf("\n");
	printf("%d人目のHP:%d\n", i, C->Hp);
	printf("%d人目のMP:%d\n", i, C->Mp);
}

アバター
h2so5
副管理人
記事: 2212
登録日時: 9年前
住所: 東京
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#2

投稿記事 by h2so5 » 4年前

6という数字はどこから出てきたのですか?

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#3

投稿記事 by みけCAT » 4年前

確保された領域の範囲外にアクセスする非常に危険なコードにしか見えないのですが、
このコードは自分で書いたのですか?それともどこかからパクったのですか?
パクったのであれば、出典を提示してください。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#4

投稿記事 by みけCAT » 4年前

sadora3 さんが書きました:なぜ+1じゃだめなのでしょうか?
+1すると確保された領域の外にアクセスすることになるので、ダメです。もちろん+6や-6もダメです。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
へにっくす
記事: 628
登録日時: 7年前
住所: 東京都

Re: このプログラムのポインタ演算について質問があります

#5

投稿記事 by へにっくす » 4年前

sadora3 さんが書きました:このプログラムの31行目と50行目のことなのですが、なぜポインタ変数(ポインタ変数というかポインタ構造体?)に+6すると次のポインタ変数を表し、-6すると前のポインタ変数を表しているのでしょう?なぜ+1じゃだめなのでしょうか?
昔のPCで16bit CPUが主流だったころ、たいてい、int型およびポインタ型のサイズは16bit=2bytesでした(例外もありましたよ)。
6という数字は、おそらくその頃の話でstruct CharactorDataのサイズを示しています(int型が2つにポインタ型が1つのため)。
これはmallocするごとにそのポインタの位置が6ずつ増えることを想定して作成されています。
なのでfreeで解放するときも1つ解放したら6加算して次へ、というような処理になってますね。
そもそもchar*のキャストもないので6というのも違いますね。ここは1でいいはずですが、そんなことより、
そもそもこんなやり方してはいけません。
mallocで得たポインタはそれぞれ独立した領域を持っており、それが連続した領域とは限りません。
私なら即にします。
こんなコードを組むこと自体、信じられない・・・(^^;
written by へにっくす

sadora3
記事: 175
登録日時: 7年前

Re: このプログラムのポインタ演算について質問があります

#6

投稿記事 by sadora3 » 4年前

>>h2so5さん
6という数字は、たまたま6にしたら思ったとおりにプログラムが動いてくれたのです。

>>みけCATさん
このプログラムは自分で書きました。危険なコードでしたか・・・。

>>へにっくすさん
やはりこれではダメですか・・・。mallocのメモリ確保は連続しないのですね。しかし、このプログラムは正常に動いてくれたのですが・・・。
ちょっと他のやり方考えてきます。警告ありがとうございました。

Poco
記事: 161
登録日時: 9年前

Re: このプログラムのポインタ演算について質問があります

#7

投稿記事 by Poco » 4年前

正常に動いたことをどうやって確認しました?
VS2013でコンパイルして、実行したら追加は(運良く?)できますが、
表示は常に最初に登録したものしか表示されませんでした。

sadora3
記事: 175
登録日時: 7年前

Re: このプログラムのポインタ演算について質問があります

#8

投稿記事 by sadora3 » 4年前

>>Pocoさん
私のPCでslnファイルでプロジェクト開いてそこで実行したら正常に動きました。生成された(Debugフォルダの中)実行ファイルで実行したらエラーになりました。どうやら私のプロジェクトでしか正常に動かなかったようです・・・。

おそらく正常に動くようになりました。このプログラムは危険も不具合もありませんよね?

コード:

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

int nNum = 1;

struct CharactorData{
	int Hp;
	int Mp;
	struct CharactorData *next;
};

void InputData(CharactorData*);
void OutputData(const CharactorData*, int);

void main(){
	CharactorData* pRoot = NULL;
	CharactorData* pTemp = NULL;
	int nSelect;

	while(1){
		printf("=====1:追加 2:表示 3:終了=====\n");
		printf("何を選びますか:");
		scanf("%d", &nSelect);
		if(nSelect == 1){
			CharactorData* pPlayer;
			pPlayer = (CharactorData*)malloc(sizeof(CharactorData));
			InputData(pPlayer);
			pPlayer->next = NULL;
			if(nNum == 1){
				pRoot = pPlayer;
			}else{
				pTemp = pRoot;
				for(int i = 2; i < nNum; i++){
					pTemp = pTemp->next;
				}
				pTemp->next = pPlayer;
			}
			nNum++;
		}
		else if(nSelect == 2){
			if(nNum > 1){
				int i = 1;
				pTemp = pRoot;
				while(pTemp != NULL){
					OutputData(pTemp, i);
					pTemp = pTemp->next;
					i++;
				}
				printf("\n");
			}else{
				printf("表示させるべきデータがありません\n");
			}
		}else if(nSelect == 3){
			while(nNum > 1){
				pTemp = pRoot;
				while(pTemp != NULL){
					pTemp = pTemp->next;
				}
				free(pTemp);
				nNum--;
			}
			break;
		}else{
			printf("追加か表示か終了以外は出来ません\n");
		}
	}

	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
}

void InputData(CharactorData* C){
	printf("\n");
	printf("%d人目のHPを入力してください:", nNum);
	scanf("%d", &C->Hp);
	printf("%d人目のMPを入力してください:", nNum);
	scanf("%d", &C->Mp);
	printf("\n");
}

void OutputData(const CharactorData* C, int i){
	printf("\n");
	printf("%d人目のHP:%d\n", i, C->Hp);
	printf("%d人目のMP:%d\n", i, C->Mp);
}

たいちう
記事: 418
登録日時: 9年前

Re: このプログラムのポインタ演算について質問があります

#9

投稿記事 by たいちう » 4年前

> おそらく正常に動くようになりました。このプログラムは危険も不具合もありませんよね?

ダメでしょ。
メモリリークを検出してないのですか?

コード:

while(pTemp != NULL){
	pTemp = pTemp->next;
}
free(pTemp);

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#10

投稿記事 by みけCAT » 4年前

sadora3 さんが書きました:このプログラムは危険も不具合もありませんよね?
変な入力を与えた時、scanfで値が更新されずに無限ループになる恐れがあります。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#11

投稿記事 by みけCAT » 4年前

たいちう さんが書きました:ダメでしょ。
メモリリークを検出してないのですか?

コード:

while(pTemp != NULL){
	pTemp = pTemp->next;
}
free(pTemp);
そこは1個ずつ消しているので、効率は悪くてもメモリリークにはならない気がします。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#12

投稿記事 by みけCAT » 4年前

sadora3 さんが書きました:このプログラムは危険も不具合もありませんよね?
確かに実際にmallocが失敗する可能性は0に近似できるかもしれないですが、mallocの戻り値がNULLでないことを確認しないと採点者に悪く思われるかもしれません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

たいちう
記事: 418
登録日時: 9年前

Re: このプログラムのポインタ演算について質問があります

#13

投稿記事 by たいちう » 4年前

> そこは1個ずつ消しているので、効率は悪くてもメモリリークにはならない気がします。

freeの引数は必ずNULLになりませんか?

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#14

投稿記事 by みけCAT » 4年前

sadora3 さんが書きました:このプログラムは危険も不具合もありませんよね?
C++ならばmain関数の戻り値はint型でなければなりません。
また、C言語ならば、構造体型を利用するには(例えばInputDataの引数は)CharactorDataではなくstruct CharactorDataと書かなければなりません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6236
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: このプログラムのポインタ演算について質問があります

#15

投稿記事 by みけCAT » 4年前

たいちう さんが書きました:> そこは1個ずつ消しているので、効率は悪くてもメモリリークにはならない気がします。

freeの引数は必ずNULLになりませんか?
あっ…確かにNULLになり、開放できなさそうですね。
また、仮に末尾の要素を開放できたとすると、次のループで無効な場所にアクセスすることになり危険ですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

sadora3
記事: 175
登録日時: 7年前

Re: このプログラムのポインタ演算について質問があります

#16

投稿記事 by sadora3 » 4年前

>>たいちうさん
申し訳ありません。出力ウィンドウのデバッグのところにちゃんとメモリリークと出ていたのに見逃していました。
ありがとうございます。

>>みけCATさん
scanfでaと入力したところ、確かに無限ループになりました。これどのように対策すればよいのでしょうか?
あと、「mallocの戻り値がNULLでないことを確認しないと」とありますが、これはどのようにやればいいのでしょうか?
あ、pPlayerがNULLかどうか調べればいいんですね。

現在このようなプログラムになりました。もうメモリリークも起きません。

コード:

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

int nNum = 1;

struct CharactorData{
	int Hp;
	int Mp;
	struct CharactorData *next;
};

void InputData(struct CharactorData*);
void OutputData(const struct CharactorData*, int);

int main(){
	struct CharactorData* pRoot = NULL;
	struct CharactorData* pTemp = NULL;
	int nSelect;

	while(1){
		printf("=====1:追加 2:表示 3:終了=====\n");
		printf("何を選びますか:");
		scanf("%d", &nSelect);
		if(nSelect == 1){
			struct CharactorData* pPlayer;
			pPlayer = (CharactorData*)malloc(sizeof(CharactorData));
			if(pPlayer != NULL){
				InputData(pPlayer);
				pPlayer->next = NULL;
				if(nNum == 1){
					pRoot = pPlayer;
				}else{
					pTemp = pRoot;
					for(int i = 2; i < nNum; i++){
						pTemp = pTemp->next;
					}
					pTemp->next = pPlayer;
				}
				nNum++;
			}
		}
		else if(nSelect == 2){
			if(nNum > 1){
				int i = 1;
				pTemp = pRoot;
				while(pTemp != NULL){
					OutputData(pTemp, i);
					pTemp = pTemp->next;
					i++;
				}
				printf("\n");
			}else{
				printf("表示させるべきデータがありません\n");
			}
		}else if(nSelect == 3){
			while(nNum > 1){
				pTemp = pRoot;
				for(int i = 2; i < nNum; i++){
					pTemp = pTemp->next;
				}
				free(pTemp);
				nNum--;
			}
			break;
		}else{
			printf("追加か表示か終了以外は出来ません\n");
		}
	}

	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	return 0;
}

void InputData(struct CharactorData* C){
	printf("\n");
	printf("%d人目のHPを入力してください:", nNum);
	scanf("%d", &C->Hp);
	printf("%d人目のMPを入力してください:", nNum);
	scanf("%d", &C->Mp);
	printf("\n");
}

void OutputData(const struct CharactorData* C, int i){
	printf("\n");
	printf("%d人目のHP:%d\n", i, C->Hp);
	printf("%d人目のMP:%d\n", i, C->Mp);
}

アバター
へにっくす
記事: 628
登録日時: 7年前
住所: 東京都

Re: このプログラムのポインタ演算について質問があります

#17

投稿記事 by へにっくす » 4年前

sadora3 さんが書きました:>>h2so5さん
6という数字は、たまたま6にしたら思ったとおりにプログラムが動いてくれたのです。
>>みけCATさん
このプログラムは自分で書きました。危険なコードでしたか・・・。
なんだ。昔のコードかと思いました…
たまたま動いたからって、それが正しいコードかは違いますよ。
私のところで動かしましたら例外で落ちました・・・
メモリリークも起きていたのだから分かりますよね?
sadora3 さんが書きました:>>みけCATさん
scanfでaと入力したところ、確かに無限ループになりました。これどのように対策すればよいのでしょうか?
scanfで %d を指定して数字だけ入力できるようにしてるからですね。
数字以外の文字を許容したいなら、文字列で受け取るようにしないといけません。
scanfの戻り値は引数に代入できた数なので、それを判定してもよろしいでしょう。→参考

コード:

if (1 != scanf("%d", &C->Hp)) { // 引数が1つなので代入できたなら1が返ってくるはず
		printf("入力できません");
		exit(0); // 強制終了。もちろんメモリリークになるはず・・・
}
written by へにっくす

sadora3
記事: 175
登録日時: 7年前

Re: このプログラムのポインタ演算について質問があります

#18

投稿記事 by sadora3 » 4年前

>>へにっくすさん
へにっくすさんの方法を試したら出来ました。ありがとうございます。
このような感じのプログラムになりました。
rewind(stdin)を突っ込んだら無限ループにならなくなったのですが、正直rewind(stdin)の意味がよく分かりません。
調べたところ、「キーボード バッファをクリアする」と出てきたのですが、どういう意味なのでしょうか?

コード:

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

int nNum = 1;

struct CharactorData{
	int Hp;
	int Mp;
	struct CharactorData *next;
};

void InputData(struct CharactorData*);
void OutputData(const struct CharactorData*, int);

int main(){
	struct CharactorData* pRoot = NULL;
	struct CharactorData* pTemp = NULL;
	int nSelect;

	while(1){
		printf("=====1:追加 2:表示 3:終了=====\n");
		printf("何を選びますか:");
		if(1 != scanf("%d", &nSelect)){
			nSelect = 0;
			rewind(stdin);
		}
		if(nSelect == 1){
			struct CharactorData* pPlayer;
			pPlayer = (CharactorData*)malloc(sizeof(CharactorData));
			if(pPlayer != NULL){
				InputData(pPlayer);
				pPlayer->next = NULL;
				if(nNum == 1){
					pRoot = pPlayer;
				}else{
					pTemp = pRoot;
					for(int i = 2; i < nNum; i++){
						pTemp = pTemp->next;
					}
					pTemp->next = pPlayer;
				}
				nNum++;
			}
		}
		else if(nSelect == 2){
			if(nNum > 1){
				int i = 1;
				pTemp = pRoot;
				while(pTemp != NULL){
					OutputData(pTemp, i);
					pTemp = pTemp->next;
					i++;
				}
				printf("\n");
			}else{
				printf("表示させるべきデータがありません\n");
			}
		}else if(nSelect == 3){
			while(nNum > 1){
				pTemp = pRoot;
				for(int i = 2; i < nNum; i++){
					pTemp = pTemp->next;
				}
				free(pTemp);
				nNum--;
			}
			break;
		}else{
			printf("追加か表示か終了以外は出来ません\n");
		}
	}

	_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

	return 0;
}

void InputData(struct CharactorData* C){
	printf("\n");
	printf("※数字を入力してください。文字が入力された場合0が代入されます\n");
	printf("%d人目のHPを入力してください:", nNum);
	if(1 != scanf("%d", &C->Hp)){
		C->Hp = 0;
		rewind(stdin);
	}
	printf("%d人目のMPを入力してください:", nNum);
	if(1 != scanf("%d", &C->Mp)){
		C->Mp = 0;
		rewind(stdin);
	}
	printf("\n");
}

void OutputData(const struct CharactorData* C, int i){
	printf("\n");
	printf("%d人目のHP:%d\n", i, C->Hp);
	printf("%d人目のMP:%d\n", i, C->Mp);
}

YuO
記事: 941
登録日時: 9年前
住所: 東京都世田谷区

Re: このプログラムのポインタ演算について質問があります

#19

投稿記事 by YuO » 4年前

sadora3 さんが書きました:scanfでaと入力したところ、確かに無限ループになりました。これどのように対策すればよいのでしょうか?
C FAQ 12.20参照。
個人的にも,scanfを使わないことをお薦めします。
自由入力を相手にするのであれば,入力の取り扱いが行指向であればfgetsを,1文字単位指向であればgetcを使います。
今回の場合はfgetsがよいでしょう。
sadora3 さんが書きました:rewind(stdin)を突っ込んだら無限ループにならなくなったのですが、正直rewind(stdin)の意味がよく分かりません。
調べたところ、「キーボード バッファをクリアする」と出てきたのですが、どういう意味なのでしょうか?
# Visual C++の現行バージョンにおける専用の動作なので,rewind(stdin);はあまり使わないことが望ましいですが……。

キーボードの入力は,改行が入力されるまで標準ライブラリの中で入力を溜めていきます。
この時,溜めておくための場所をバッファと呼びます。

そして,改行が入力されたタイミングで,標準入力関数にデータを引き渡します。
scanfなどでは,引き渡されたうちの必要な分のデータだけをバッファから取り出して使い,残ったデータはバッファの中に残しておきます。
%dに対してaなどが該当した場合,「必要な分のデータ」にaが含まれないのでバッファにはaが残り続けます。
rewind(stdin);によって,Visual C++ (の現行バージョンやそれ以前のバージョン) では,残っているaがなくなります。
ref) rewind (MSDN)

sadora3
記事: 175
登録日時: 7年前

Re: このプログラムのポインタ演算について質問があります

#20

投稿記事 by sadora3 » 4年前

>>YuOさん
貴重な情報をありがとうございます。とても参考になりました。

回答してくれた皆さん本当にありがとうございました。
また何かあれば、そのときもどうぞよろしくお願いします。

閉鎖

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