ページ 1 / 1
このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 20:03
by sadora3
このプログラムの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);
}
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 21:30
by h2so5
6という数字はどこから出てきたのですか?
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 21:54
by みけCAT
確保された領域の範囲外にアクセスする非常に危険なコードにしか見えないのですが、
このコードは自分で書いたのですか?それともどこかからパクったのですか?
パクったのであれば、出典を提示してください。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 21:56
by みけCAT
sadora3 さんが書きました:なぜ+1じゃだめなのでしょうか?
+1すると確保された領域の外にアクセスすることになるので、ダメです。もちろん+6や-6もダメです。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 22:15
by へにっくす
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で得たポインタはそれぞれ独立した領域を持っており、それが連続した領域とは限りません。
私なら即
没にします。
こんなコードを組むこと自体、信じられない・・・(^^;
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 22:54
by sadora3
>>h2so5さん
6という数字は、たまたま6にしたら思ったとおりにプログラムが動いてくれたのです。
>>みけCATさん
このプログラムは自分で書きました。危険なコードでしたか・・・。
>>へにっくすさん
やはりこれではダメですか・・・。mallocのメモリ確保は連続しないのですね。しかし、このプログラムは正常に動いてくれたのですが・・・。
ちょっと他のやり方考えてきます。警告ありがとうございました。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月19日(火) 23:58
by Poco
正常に動いたことをどうやって確認しました?
VS2013でコンパイルして、実行したら追加は(運良く?)できますが、
表示は常に最初に登録したものしか表示されませんでした。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:04
by sadora3
>>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);
}
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:34
by たいちう
> おそらく正常に動くようになりました。このプログラムは危険も不具合もありませんよね?
ダメでしょ。
メモリリークを検出してないのですか?
コード:
while(pTemp != NULL){
pTemp = pTemp->next;
}
free(pTemp);
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:40
by みけCAT
sadora3 さんが書きました:このプログラムは危険も不具合もありませんよね?
変な入力を与えた時、scanfで値が更新されずに無限ループになる恐れがあります。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:41
by みけCAT
たいちう さんが書きました:ダメでしょ。
メモリリークを検出してないのですか?
コード:
while(pTemp != NULL){
pTemp = pTemp->next;
}
free(pTemp);
そこは1個ずつ消しているので、効率は悪くてもメモリリークにはならない気がします。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:42
by みけCAT
sadora3 さんが書きました:このプログラムは危険も不具合もありませんよね?
確かに実際にmallocが失敗する可能性は0に近似できるかもしれないですが、mallocの戻り値がNULLでないことを確認しないと採点者に悪く思われるかもしれません。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:47
by たいちう
> そこは1個ずつ消しているので、効率は悪くてもメモリリークにはならない気がします。
freeの引数は必ずNULLになりませんか?
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:47
by みけCAT
sadora3 さんが書きました:このプログラムは危険も不具合もありませんよね?
C++ならば
main関数の戻り値はint型でなければなりません。
また、C言語ならば、構造体型を利用するには(例えばInputDataの引数は)CharactorDataではなくstruct CharactorDataと書かなければなりません。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 00:48
by みけCAT
たいちう さんが書きました:> そこは1個ずつ消しているので、効率は悪くてもメモリリークにはならない気がします。
freeの引数は必ずNULLになりませんか?
あっ…確かにNULLになり、開放できなさそうですね。
また、仮に末尾の要素を開放できたとすると、次のループで無効な場所にアクセスすることになり危険ですね。
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 02:02
by sadora3
>>たいちうさん
申し訳ありません。出力ウィンドウのデバッグのところにちゃんとメモリリークと出ていたのに見逃していました。
ありがとうございます。
>>みけ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);
}
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 06:09
by へにっくす
sadora3 さんが書きました:>>h2so5さん
6という数字は、たまたま6にしたら思ったとおりにプログラムが動いてくれたのです。
>>みけCATさん
このプログラムは自分で書きました。危険なコードでしたか・・・。
なんだ。昔のコードかと思いました…
たまたま動いたからって、それが正しいコードかは違いますよ。
私のところで動かしましたら例外で落ちました・・・
メモリリークも起きていたのだから分かりますよね?
sadora3 さんが書きました:>>みけCATさん
scanfでaと入力したところ、確かに無限ループになりました。これどのように対策すればよいのでしょうか?
scanfで %d を指定して数字だけ入力できるようにしてるからですね。
数字以外の文字を許容したいなら、文字列で受け取るようにしないといけません。
scanfの戻り値は引数に代入できた数なので、それを判定してもよろしいでしょう。→
参考
コード:
if (1 != scanf("%d", &C->Hp)) { // 引数が1つなので代入できたなら1が返ってくるはず
printf("入力できません");
exit(0); // 強制終了。もちろんメモリリークになるはず・・・
}
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 13:28
by sadora3
>>へにっくすさん
へにっくすさんの方法を試したら出来ました。ありがとうございます。
このような感じのプログラムになりました。
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);
}
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月20日(水) 16:11
by YuO
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)
Re: このプログラムのポインタ演算について質問があります
Posted: 2015年5月21日(木) 00:00
by sadora3
>>YuOさん
貴重な情報をありがとうございます。とても参考になりました。
回答してくれた皆さん本当にありがとうございました。
また何かあれば、そのときもどうぞよろしくお願いします。