文字列についての質問とコンパイル時の警告について

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

文字列についての質問とコンパイル時の警告について

#1

投稿記事 by surf » 14年前

初めまして、プログラミングを始めて3ヶ月ほどの初心者です。
C言語とDXライブラリを使用してアクションRPGを作ろうとしています。

現在ファイル読み込みのプログラムを作っていて、あやふやだった文字列の扱いが
さらにわからなくなったので質問させていただきます。
まずはソースコードを。

コード:

#include "DxLib.h"
#include <stdlib.h>
#include <string.h>

typedef struct{
	int id;
	char *pass;		//問題の部分
	char *chip;
	int x,y;
}List_t;

int num;
List_t *list;

 
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){
	if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
	SetDrawScreen( DX_SCREEN_BACK ) ;

	int i , list_file;
	char str[256];

	list_file = FileRead_open("maplist.csv");	

	FileRead_gets( str, 256, list_file ) ;
	num = atoi(strtok( str, ",\n" ));			//最初の一行にリストの数が記載されているのでnumに代入

	list = (List_t *)malloc(sizeof(List_t) * num);	//リストの数値分メモリを確保
	if (list == NULL) exit(0);

	for( i=0 ; i<num ; i++ ){

		FileRead_gets( str, 256, list_file ) ;	//一行読み込み

		list[i].id = atoi(strtok( str, ",\n" ));       //文字列を分解してそれぞれ代入
		strcpy(list[i].pass , strtok( NULL , ",\n" ));
		strcpy(list[i].chip , strtok( NULL , ",\n" ));
		list[i].x = atoi(strtok( NULL , ",\n" ));
		list[i].y = atoi(strtok( NULL , ",\n" ));
	}

	FileRead_close(list_file);

	printfDx("%d\n",num);			//結果の表示

	for( i=0 ; i<num ; i++ ){
		printfDx("%d",list[i].id);
		printfDx("%s",list[i].pass);
		printfDx("%s",list[i].chip);
		printfDx("%d",list[i].x);
		printfDx("%d\n",list[i].y);		
	}

	free(list);

	ScreenFlip() ;
	WaitKey() ;

	DxLib_End() ;
        return 0 ;
}
ちなみにCSVファイルはこんな感じです。
2
4,test60x60.csv,euro1.png,40,2
5,test30x30.csv,euro1.png,18,27

以上のコードを実行するとデバックエラーとでてまともに動きません。
そこで自分なりに色々調べてコメントで問題の部分と書いてあるところを下記のように書き直すと、
予定通りの結果が得られました。

コード:

typedef struct{
	int id;
	char pass[30];		//問題の部分
	char chip[30];		//適当にファイルパス以上の長さの配列を宣言
	int x,y;
}List_t;
で、なぜ疑問がわいたのかというとファイル読み込みをする前は直接ソースコードに書いていまして、
その時の書き方が

コード:

typedef struct{
	int id;
	char *pass;		//問題の部分
	char *chip;
	int x,y;
}List_t;
List_t list[2] = {
	{ 4 , test60x60.csv , euro1.png ,40 ,2  },
	{ 5 , test30x30.csv , euro1.png ,18 ,17 }
};
のような感じで定義時に直接初期化して書いていて、文字列のあつかいについては
char *pass = "abcd"
と、
char pass[5]= "abcd" , char pass[] = "abcd"
は、一緒の事だと解釈していました。
で今回の事でどうも違う感じがしてあやふやだったのが、さらにこんがらがってきたので質問させて頂きました。
一応自分なりの考えとしましては、

ポインタを使うときは宣言の時に代入する時にしか使えなくて後から文字も変えることができない。(できるけど何が起こるかわからない)
一方、配列で要素数を決めていれば、後から文字を入れる事もできるし、変えることもできる。

と、思っています。
この考えはあっているのでしょうか? 間違っていれば指摘して頂けると幸いです。

すみませんが、あともう一つあります。
上記のファイル読み込みプログラムを実装してもエラーはでないのですが警告がほぼ毎回でます。

1>c:\users\go\desktop\製作\dxsample\vc\sample.cpp(27) : warning C4996: 'strtok':
This function or variable may be unsafe. Consider using strtok_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
See online help for details.
1> c:\program files\microsoft visual studio 9.0\vc\include\string.h(166) : 'strtok' の宣言を確認してください。

この文がstrtokを使った数だけでます。実は以前はファイル読み込み関数に標準関数?のfopen関数などをつかっていたのですが
その時も似たような警告が出ていました。DXライブラリのファイル操作関数を使うようにしたら警告はでなくなったので
そのまま放置していましたが・・・・。
この警告は一体なんなのでしょうか?

長くなりましたがご教授頂けるとありがたいです。
よろしくお願いします。

surf

Re: 文字列についての質問とコンパイル時の警告について

#2

投稿記事 by surf » 14年前

すみません環境を書き忘れていました。
windows7、VisualC++ 2008 Express Edition です。
よろしくお願いします。

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

Re: 文字列についての質問とコンパイル時の警告について

#3

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

surf さんが書きました:すみませんが、あともう一つあります。
上記のファイル読み込みプログラムを実装してもエラーはでないのですが警告がほぼ毎回でます。

1>c:\users\go\desktop\製作\dxsample\vc\sample.cpp(27) : warning C4996: 'strtok':
This function or variable may be unsafe. Consider using strtok_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS.
See online help for details.
1> c:\program files\microsoft visual studio 9.0\vc\include\string.h(166) : 'strtok' の宣言を確認してください。

この文がstrtokを使った数だけでます。実は以前はファイル読み込み関数に標準関数?のfopen関数などをつかっていたのですが
その時も似たような警告が出ていました。DXライブラリのファイル操作関数を使うようにしたら警告はでなくなったので
そのまま放置していましたが・・・・。
この警告は一体なんなのでしょうか?
これはマイクロソフトが「安全」な、ほかの環境との互換性がない独自の関数を使えと言っているだけです。
Dev-C++を使えばこのような警告は出ません。
英語版http://www.bloodshed.net/devcpp.html
日本語版http://sourceforge.jp/projects/dev-cpp-jp/
ポータブル(USBメモリーなどで使える)版http://sourceforge.net/projects/devcpp-portable/
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: 文字列についての質問とコンパイル時の警告について

#4

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

surf さんが書きました:文字列のあつかいについては
char *pass = "abcd"
と、
char pass[5]= "abcd" , char pass[] = "abcd"
は、一緒の事だと解釈していました。
で今回の事でどうも違う感じがしてあやふやだったのが、さらにこんがらがってきたので質問させて頂きました。
一応自分なりの考えとしましては、

ポインタを使うときは宣言の時に代入する時にしか使えなくて後から文字も変えることができない。(できるけど何が起こるかわからない)
一方、配列で要素数を決めていれば、後から文字を入れる事もできるし、変えることもできる。

と、思っています。
この考えはあっているのでしょうか? 間違っていれば指摘して頂けると幸いです。
char *pass="abcd";
では、passに"abcd"のポインタを入れているだけであり、"abcd"の実体はpassとは別の場所にあります。
後でpass="efgh";のように別のポインタを代入すれば、文字を変えることができるはずです。

char pass[5]="abcd";は、char pass[5]={'a','b','c','d','\0'};と同じ意味です。
char pass[]="abcd";も、char pass[]={'a','b','c','d','\0'};と同じ意味です。
参考(苦C)http://9cguide.appspot.com/14-02.html
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

surf

Re: 文字列についての質問とコンパイル時の警告について

#5

投稿記事 by surf » 14年前

これはマイクロソフトが「安全」な、ほかの環境との互換性がない独自の関数を使えと言っているだけです。
Dev-C++を使えばこのような警告は出ません。
他の環境では使えるという事は別に「危険」という事ではなかったのですね。

char *pass="abcd";
では、passに"abcd"のポインタを入れているだけであり、"abcd"の実体はpassとは別の場所にあります。
後でpass="efgh";のように別のポインタを代入すれば、文字を変えることができるはずです。
なろほど、そういえばそうですよね。
char *pass="abcd" と、 char pass[]="abcd"が一緒のわけないですよね。

そして新たな疑問がわいたのですが、 char *pass="abcd" のpassにはabcdのアドレスが入っていて
pass="efgh"のように別のアドレスを入れたら前のabcdの実体はメモリ上から消えるのですか?
それともどこかに存在し続けるのですか?

あと今回のデバックエラーは
http://www9.plala.or.jp/sgwr-t/c/sec10-3.html
こちらのサイトを参考にして解決できたのですが、このサイトのページの下のほうに書いてあるように
他の環境では動いていたかも知れないと言う事ですか?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#6

投稿記事 by softya(ソフト屋) » 14年前

surf さんが書きました:そして新たな疑問がわいたのですが、 char *pass="abcd" のpassにはabcdのアドレスが入っていて
pass="efgh"のように別のアドレスを入れたら前のabcdの実体はメモリ上から消えるのですか?
それともどこかに存在し続けるのですか?
定数ですので、プログラムの定数データとしてずっとメモリに存在します。
surf さんが書きました:あと今回のデバックエラーは
http://www9.plala.or.jp/sgwr-t/c/sec10-3.html
こちらのサイトを参考にして解決できたのですが、このサイトのページの下のほうに書いてあるように
他の環境では動いていたかも知れないと言う事ですか?
どう解決したのでしょうか?
ここのルールなのですが解決したコードの掲載をお願いします。

ちなみに strcpy(list.pass , strtok( NULL , ",\n" ));がポインタ不定ですので、どう動くかは環境依存です。
OSやMMUが強力な環境ではメモリ保護例外となる可能性が高いでしょう。ただ、書き換え可能なメモリの値を書き換えてしまう可能性もZEROではありません。
マイコン等だとどうなるかは運次第です。何もないメモリに書きこんだふりをして参照するとゴミしか出てこない可能性がありますし、プログラムやデータ、簡易OSをデータ破壊する可能性もあります。

書き忘れたので追記です。
surf さんが書きました:他の環境では使えるという事は別に「危険」という事ではなかったのですね。


いえ、危険だと思います。
ただ、C言語の規格ISO/JISには有りませんのでマイクロソフト独自の判断です。
バッファーオーバーランの危険と隣り合わせだという自覚は持って下さい。警告するだけの意味はあるのです。
strtokも文字列バッファを書き換えるやっかいな関数です。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

Loki

Re: 文字列についての質問とコンパイル時の警告について

#7

投稿記事 by Loki » 14年前

警告についてですが、バッファオーバーランの可能性があるため、警告が出ます。
警告が邪魔なら、
#define _CRT_SECURE_NO_WARNINGS
を一番(includeより)上に入れることによって、消えると思います。

surf

Re: 文字列についての質問とコンパイル時の警告について

#8

投稿記事 by surf » 14年前

softya(ソフト屋) さんが書きました: 定数ですので、プログラムの定数データとしてずっとメモリに存在します。
では、char *pass="abcd"に"efgh"のように別のアドレスを入れたら元の"abcd"は文字型配列に入っているわけでもなく
アドレスを保持していた物も変わってしまったから取り出すことも参照する事もできずメモリの無駄となるわけですか?
なので文字列が変わる可能性のある物は文字型配列に入れたほうがいいという解釈であっていますか?
何かしょうもない疑問ばかりですみません。
softya(ソフト屋) さんが書きました: どう解決したのでしょうか?
ここのルールなのですが解決したコードの掲載をお願いします。
最初の投稿時に修正した部分だけ載せていたのですが全部載せますね。

コード:

#include "DxLib.h"
#include <stdlib.h>
#include <string.h>

typedef struct{
	int id;
	char pass[30];		//修正した部分。文字型ポインタから文字型配列に変更
	char chip[30];
	int x,y;
}List_t;

int num;
List_t *list;

 
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){
	if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
	SetDrawScreen( DX_SCREEN_BACK ) ;

	int i , list_file;
	char str[256];

	list_file = FileRead_open("maplist.csv");	

	FileRead_gets( str, 256, list_file ) ;
	num = atoi(strtok( str, ",\n" ));			//最初の一行にリストの数が記載されているのでnumに代入

	list = (List_t *)malloc(sizeof(List_t) * num);	//リストの数値分メモリを確保
	if (list == NULL) exit(0);

	for( i=0 ; i<num ; i++ ){

		FileRead_gets( str, 256, list_file ) ;	//一行読み込み

		list[i].id = atoi(strtok( str, ",\n" ));
		strcpy(list[i].pass , strtok( NULL , ",\n" ));
		strcpy(list[i].chip , strtok( NULL , ",\n" ));
		list[i].x = atoi(strtok( NULL , ",\n" ));
		list[i].y = atoi(strtok( NULL , ",\n" ));
	}

	FileRead_close(list_file);

	printfDx("%d\n",num);			//結果の表示

	for( i=0 ; i<num ; i++ ){
		printfDx("%d",list[i].id);
		printfDx("%s",list[i].pass);
		printfDx("%s",list[i].chip);
		printfDx("%d",list[i].x);
		printfDx("%d\n",list[i].y);		
	}

	free(list);

	ScreenFlip() ;
	WaitKey() ;

	DxLib_End() ;
        return 0 ;
}
修正した部分は構造体宣言の中の
char *pass;
char *chip;

char pass[30];
char chip[30];
に変えただけです。
softya(ソフト屋) さんが書きました: ちなみに strcpy(list.pass , strtok( NULL , ",\n" ));がポインタ不定ですので、どう動くかは環境依存です。
OSやMMUが強力な環境ではメモリ保護例外となる可能性が高いでしょう。ただ、書き換え可能なメモリの値を書き換えてしまう可能性もZEROではありません。
マイコン等だとどうなるかは運次第です。何もないメモリに書きこんだふりをして参照するとゴミしか出てこない可能性がありますし、プログラムやデータ、簡易OSをデータ破壊する可能性もあります。


すみません、strcpy(list.pass , strtok( NULL , ",\n" ));がポインタ不定という意味がわかりません。
そもそも書き方が間違っているという事ですか?

softya(ソフト屋) さんが書きました: いえ、危険だと思います。
ただ、C言語の規格ISO/JISには有りませんのでマイクロソフト独自の判断です。
バッファーオーバーランの危険と隣り合わせだという自覚は持って下さい。警告するだけの意味はあるのです。
strtokも文字列バッファを書き換えるやっかいな関数です。


バッファーオーバーランという意味が解らなくてちょっと調べてみました。これをすべて理解するには時間が掛かりそうです。
今回の場合は用意されたメモリサイズ以上の文字列が入力されるかもしれない危険があるから警告がでたという事ですか?
この問題を解決するため警告文をよく見たら Consider using strtok_s instead. という文があったのでstrtok_sと言う関数を使えばいいのでしょうか?

Loki さんが書きました: 警告が邪魔なら、
#define _CRT_SECURE_NO_WARNINGS
を一番(includeより)上に入れることによって、消えると思います。


#define _CRT_SECURE_NO_WARNINGSを入れてみたら警告が消えました。
教えていただきありがとうございます。

beatle
記事: 1281
登録日時: 14年前
住所: 埼玉
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#9

投稿記事 by beatle » 14年前

surf さんが書きました:
softya(ソフト屋) さんが書きました: 定数ですので、プログラムの定数データとしてずっとメモリに存在します。
では、char *pass="abcd"に"efgh"のように別のアドレスを入れたら元の"abcd"は文字型配列に入っているわけでもなく
アドレスを保持していた物も変わってしまったから取り出すことも参照する事もできずメモリの無駄となるわけですか?
なので文字列が変わる可能性のある物は文字型配列に入れたほうがいいという解釈であっていますか?
何かしょうもない疑問ばかりですみません。
メモリの無駄といえばその通りですね.
ただし

コード:

char pass[] = "abcd";
strcpy(pass, "efgh");
と書いたとしても,結局"abcd"とか"efgh"はメモリ上に残ります.

コード:

char pass[5];
pass[0] = 'a';
pass[1] = 'b';
pass[2] = 'c';
pass[3] = 'd';
pass[4] = '\0';
pass[0] = 'e';
pass[1] = 'f';
pass[2] = 'g';
pass[3] = 'h';
pass[4] = '\0';
と書いた場合は,確かに"abcd"や"efgh"というデータは生成されません.が,実行コード領域に(move命令と共に)'a'や'b'などの文字定数データが埋め込まれますので,strcpyバージョンに比べてさらに悪いメモリ効率になります.

結局,固定した文字データを扱うなら,素直にchar*ポインタにしてchar* pass = "abcd";などとするのが最も良いと思います.
surf さんが書きました: すみません、strcpy(list.pass , strtok( NULL , ",\n" ));がポインタ不定という意味がわかりません。
そもそも書き方が間違っているという事ですか?

list.passが初期化されておらず,不正なポインタになっているという意味でしょう.softyaさんの指摘は,おそらく

コード:

typedef struct{
    int id;
    char *pass;     //問題の部分
    char *chip;
    int x,y;
}List_t;
の場合に対するものです.

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#10

投稿記事 by softya(ソフト屋) » 14年前

surf さんが書きました: では、char *pass="abcd"に"efgh"のように別のアドレスを入れたら元の"abcd"は文字型配列に入っているわけでもなく
アドレスを保持していた物も変わってしまったから取り出すことも参照する事もできずメモリの無駄となるわけですか?
なので文字列が変わる可能性のある物は文字型配列に入れたほうがいいという解釈であっていますか?
何かしょうもない疑問ばかりですみません。
必要だから定義するのであって、1回しか使わないから不要などと考える必要はないと思います。
メモリ節約という意味では、そのぐらいはささやかな問題ですのでもっと大きなメモリの浪費に気を配った方が良いでしょう。

>ちなみに、なので文字列が変わる可能性のある物は文字型配列に入れたほうがいいという解釈であっていますか?
残念ながら文字列型配列にしても実は定数からコピーされるコンパイラが多いと思います。
なので却って浪費します。
surf さんが書きました: すみません、strcpy(list.pass , strtok( NULL , ",\n" ));がポインタ不定という意味がわかりません。
そもそも書き方が間違っているという事ですか?


beatleさんの言われる通り最初のソースコードの問題点を書きました。
mallocしたメモリの値は不定ですので、
char *pass; //問題の部分
と定義しただけの場合ポインタ値が何を指しているかは不定です。

surf さんが書きました: バッファーオーバーランという意味が解らなくてちょっと調べてみました。これをすべて理解するには時間が掛かりそうです。
今回の場合は用意されたメモリサイズ以上の文字列が入力されるかもしれない危険があるから警告がでたという事ですか?
この問題を解決するため警告文をよく見たら Consider using strtok_s instead. という文があったのでstrtok_sと言う関数を使えばいいのでしょうか?


>今回の場合は用意されたメモリサイズ以上の文字列が入力されるかもしれない危険があるから警告がでたという事ですか?
strtokには文字列バッファをはみ出す危険性が常につきまといます。
使うのはstrtokで構いません。危険性を理解していれば問題はないのです。
strtok_sはVC++の環境限定ですので、DXライブラリの他の対応環境であるBorlandC++やDev-C++で作っている人には使えないコードになりますので、覚えておいて下さい。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

surf

Re: 文字列についての質問とコンパイル時の警告について

#11

投稿記事 by surf » 14年前

softya(ソフト屋)さん、beatleさん返信どうもありがとうございます。
beatle さんが書きました: list.passが初期化されておらず,不正なポインタになっているという意味でしょう.
softya(ソフト屋) さんが書きました: beatleさんの言われる通り最初のソースコードの問題点を書きました。
mallocしたメモリの値は不定ですので、
char *pass; //問題の部分
と定義しただけの場合ポインタ値が何を指しているかは不定です。


お二方に指摘されて、そもそも最初のコードのエラーは文字列云々の話ではなかったと言うことに気づきました。
そこでList_t型のメンバを初期化しようと色々試したのですがどれもうまくいきません。
また構造体の動的確保でメンバにポインタをふくむ場合の初期化の仕方を色々なサイトを見たのですが
自分の調べ方が悪いのかわかりませんでした。

ですが一応最初のコードを自分なりに改良して動くようにはなりました。
以下ソースコードです。

コード:

#define _CRT_SECURE_NO_WARNINGS
#include "DxLib.h"
#include <stdlib.h>
#include <string.h>

#define PASS_MAX 30

typedef struct{
	int id;
	char *pass;		//問題の部分
	char *chip;
	int x,y;
}List_t;

int num;
List_t *list;

 
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){
	if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
	SetDrawScreen( DX_SCREEN_BACK ) ;

	int i , list_file;
	char str[256];

	list_file = FileRead_open("maplist.csv");	

	FileRead_gets( str, 256, list_file ) ;
	num = atoi(strtok( str, ",\n" ));			//最初の一行にリストの数が記載されているのでnumに代入

	list = (List_t *)malloc(sizeof(List_t) * num);	//リストの数値分メモリを確保
	if (list == NULL) exit(0);

	for( i=0 ; i<num ; i++ ){
		list[i].pass = (char *)malloc(sizeof(char) * PASS_MAX);		//追加部分
		list[i].chip = (char *)malloc(sizeof(char) * PASS_MAX);
	}

	for( i=0 ; i<num ; i++ ){

		FileRead_gets( str, 256, list_file ) ;	//一行読み込み

		list[i].id = atoi(strtok( str, ",\n" ));
		strcpy(list[i].pass , strtok( NULL , ",\n" ));
		strcpy(list[i].chip , strtok( NULL , ",\n" ));
		list[i].x = atoi(strtok( NULL , ",\n" ));
		list[i].y = atoi(strtok( NULL , ",\n" ));
	}

	FileRead_close(list_file);

	printfDx("%d\n",num);			//結果の表示

	for( i=0 ; i<num ; i++ ){
		printfDx("%d",list[i].id);
		printfDx("%s",list[i].pass);
		printfDx("%s",list[i].chip);
		printfDx("%d",list[i].x);
		printfDx("%d\n",list[i].y);		
	}

	for( i=0 ; i<num ; i++ ){		//追加部分
		free(list[i].pass);
		free(list[i].chip);
	}

	free(list);

	ScreenFlip() ;
	WaitKey() ;

	DxLib_End() ;
    return 0 ;
}
コメントで追加部分と書いたところでchar型ポインタのメモリを確保してみました。
ただ、これだと自分の中では納得いってないので今回の場合の構造体の初期化のしかたを教えて頂けないでしょうか?
参考になるサイトでもかまわないのでお願いします。

文字列については今回の失敗のおかげで前より理解度が深まりました。
回答して頂いた方々どうもありがとうございます。
softya(ソフト屋) さんが書きました: strtokには文字列バッファをはみ出す危険性が常につきまといます。
使うのはstrtokで構いません。危険性を理解していれば問題はないのです。
strtok_sはVC++の環境限定ですので、DXライブラリの他の対応環境であるBorlandC++やDev-C++で作っている人には使えないコードになりますので、覚えておいて下さい。
危険性について色々調べていたらstrtok以外にも文字列を扱う関数はセキュリティ的に問題のある物が多いみたいですね。
余裕がでてきたらその辺りも考慮してプログラミングしていきたいと思います。
詳しく教えて頂きどうもありがとうございました。

surf

Re: 文字列についての質問とコンパイル時の警告について

#12

投稿記事 by surf » 14年前

連投してすみませんが、その後コードを色々いじっていたらうまい具合に動くものができました。
全部貼り付けると長くなるので変更部分だけのせます。

コード:

for( i=0 ; i<num ; i++ ){

		FileRead_gets( str, 256, list_file ) ;	//一行読み込み

		list[i].id = atoi(strtok( str, ",\n" ));
		list[i].pass = strtok( NULL , ",\n" );		//変更部分
		list[i].chip = strtok( NULL , ",\n" );
		list[i].x = atoi(strtok( NULL , ",\n" ));
		list[i].y = atoi(strtok( NULL , ",\n" ));
}
今回はchar型ポインタのmallocとfreeはせずに実行できました。
変更部分はstrcpyを使っていません。
strcpyの使い方が間違っていたのでしょうか?

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#13

投稿記事 by ISLe » 14年前

surf さんが書きました:連投してすみませんが、その後コードを色々いじっていたらうまい具合に動くものができました。
ホントですか?
結果表示のpassとchipは最後に読み込んだものばかり続けて表示されていないですか?
そしてそこに表示されているのはstr配列変数に残っているゴミです。
もしも読み込み部分が別関数でstr配列変数が一時変数だったら何が起きるか分かりません。

このプログラムではList_t構造体のpass,chipをポインタから配列に変更するのが最適だと思いますけど。
なぜポインタにすることにこだわるのでしょうか。
というかポインタもきちんと理解する必要があるのではないでしょうか。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#14

投稿記事 by softya(ソフト屋) » 14年前

FileRead_close(list_file);
の後に
memset(str,0xcc,sizeof(str));
を入れてみて下さい。ちゃんと動きますか?
surf さんが書きました:お二方に指摘されて、そもそも最初のコードのエラーは文字列云々の話ではなかったと言うことに気づきました。
そこでList_t型のメンバを初期化しようと色々試したのですがどれもうまくいきません。
また構造体の動的確保でメンバにポインタをふくむ場合の初期化の仕方を色々なサイトを見たのですが
自分の調べ方が悪いのかわかりませんでした。
メンバの初期化は、一つ一つ値を入れて下さい。
メンバがポインタであればポインタ毎に必要なサイズをmallocする必要があります。

ここの「第10章 ポインタ」を読んでみて下さい。「第15章 構造体」も読んだほうが良いかも。
「初心者のためのポイント学習C言語」
http://www9.plala.or.jp/sgwr-t/index.html
「ポインタ虎の巻」
http://www.nurs.or.jp/~sug/soft/tora/index.htm
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

surf

Re: 文字列についての質問とコンパイル時の警告について

#15

投稿記事 by surf » 14年前

ISLe さんが書きました: ホントですか?
結果表示のpassとchipは最後に読み込んだものばかり続けて表示されていないですか?
そしてそこに表示されているのはstr配列変数に残っているゴミです。
もしも読み込み部分が別関数でstr配列変数が一時変数だったら何が起きるか分かりません。
本当です。
下記のコードを自分のPCでは何回実行しても予想通りの結果が表示されます。

コード:

#define _CRT_SECURE_NO_WARNINGS
#include "DxLib.h"
#include <stdlib.h>
#include <string.h>

typedef struct{
	int id;
	char *pass;		//問題の部分
	char *chip;
	int x,y;
}List_t;

int num;
List_t *list;

 
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){
	if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
	SetDrawScreen( DX_SCREEN_BACK ) ;

	int i , list_file;
	char str[256];

	list_file = FileRead_open("maplist.csv");	

	FileRead_gets( str, 256, list_file ) ;
	num = atoi(strtok( str, ",\n" ));			//最初の一行にリストの数が記載されているのでnumに代入

	list = (List_t *)malloc(sizeof(List_t) * num);	//リストの数値分メモリを確保
	if (list == NULL) exit(0);

	for( i=0 ; i<num ; i++ ){

		FileRead_gets( str, 256, list_file ) ;	//一行読み込み

		list[i].id = atoi(strtok( str, ",\n" ));
		list[i].pass = strtok( NULL , ",\n" );		//変更部分
		list[i].chip = strtok( NULL , ",\n" );
		list[i].x = atoi(strtok( NULL , ",\n" ));
		list[i].y = atoi(strtok( NULL , ",\n" ));
	}

	FileRead_close(list_file);


	printfDx("%d\n",num);			//結果の表示

	for( i=0 ; i<num ; i++ ){
		printfDx("%d",list[i].id);
		printfDx("%s",list[i].pass);
		printfDx("%s",list[i].chip);
		printfDx("%d",list[i].x);
		printfDx("%d\n",list[i].y);		
	}


	free(list);

	ScreenFlip() ;
	WaitKey() ;

	DxLib_End() ;
    return 0 ;
ただこのコードは自分でも問題ないコードだとは思ってもなく
ISLeさんが仰る通り大問題なコードだと思います。
ISLe さんが書きました: このプログラムではList_t構造体のpass,chipをポインタから配列に変更するのが最適だと思いますけど。
なぜポインタにすることにこだわるのでしょうか。
というかポインタもきちんと理解する必要があるのではないでしょうか。
一応最初の投稿時にも書いたように配列に変更してあるコードもあります。そちらは自分では問題ないとは思っているのですが正直自信はありません。
ポインタにこだわるのは今回のエラーの原因は実はポインタの使い方が間違っていたという事と
自分のプログラミングの知識がまだ始めて日も浅く乏しいのでポインタの理解を深めるためです。
softya(ソフト屋) さんが書きました: FileRead_close(list_file);
の後に
memset(str,0xcc,sizeof(str));
を入れてみて下さい。ちゃんと動きますか?
このレスのコードの指定部分にmemset(str,0xcc,sizeof(str));を入れてみた所、意味不明の文字がたくさん表示されました。 今の自分ではこの関数が何をしているのかまったくわかりませんけど、これがISLeさんが仰る本来表示される結果なんですか?

今回の最初のエラーの原因を直すため自分の解らない所をちょっとまとめてみました。
List_t型のchar*は初期化されずどこを指してるのか不定大きさも不定な状態でmallocされたのでエラーが起きた。
だからmallocするまでに初期化をしてサイズを決めなければならない。
しかしながらList_t *list;ではList_t型のポインタ変数を宣言しただけなので実体はまだない。
実体はないのにどうやって初期化するのか?
またはchar*不定のままmallocしてとりあえず実体を作りその後初期化でかまわないのか?(なんかこれは間違ってるような気もしますけど・・・・)
以上のように考えて悩んでいるんですが、根本的に何か間違っているのでしょうか?

あと今回のプログラムのようにファイル読み込みをする前の構造体の初期化は

コード:

typedef struct{
    int id;
    char *pass;
    char *chip;
    int x,y;
}List_t;
List_t list[2] = {
    { 4 , "test60x60.csv" , "euro1.png" ,40 ,2  },
    { 5 , "test30x30.csv" , "euro1.png" ,18 ,17 }
};
のように初期化をしていたのですがこれも間違っているのでしょうか?
softya(ソフト屋) さんが書きました: ここの「第10章 ポインタ」を読んでみて下さい。「第15章 構造体」も読んだほうが良いかも。
「初心者のためのポイント学習C言語」
http://www9.plala.or.jp/sgwr-t/index.html
「ポインタ虎の巻」
http://www.nurs.or.jp/~sug/soft/tora/index.htm
「初心者のためのポイント学習C言語」は大変お世話になっているサイトの一つですがもう一度重点的に読み直してみます。
「ポインタ虎の巻」は一応さらっとは読んでいるんですがまだ自分のレベルには難解すぎて最初のほうしか読んでいませんでした。もっと深く読み進めていこうと思います。


ご迷惑をおかけしていると思いますがアドバイス頂けるとありがたいです。
よろしくお願いします。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#16

投稿記事 by ISLe » 14年前

surf さんが書きました:下記のコードを自分のPCでは何回実行しても予想通りの結果が表示されます。
最初の質問文にあるcsvデータをこちらで表示してみると

2
4test30x30.csveuro1.png402
5test30x30.csveuro1.png1827

と表示されました。
わたしは

2
4test60x60.csveuro1.png402
5test30x30.csveuro1.png1827

と表示されなければおかしいと思うのですけどsurfさんが予想通りとおっしゃるのならそれで良いのでしょう。


ポインタはデータの入っている場所を記憶するものです。
str変数に1行ずつ読み込んでその都度場所を覚えていても、ループを抜けたときそこに残っているのは最後に読み込んだ行です。
softyaさんのmemsetの指示はこの残っているデータを0xccというcharデータで埋め尽くして破壊するものです。

コード:

List_t list[2] = {
    { 4 , "test60x60.csv" , "euro1.png" ,40 ,2  },
    { 5 , "test30x30.csv" , "euro1.png" ,18 ,17 }
};
既出ですが文字列リテラルはプログラムの開始から終了まで同じ場所にあるので問題ありません。
しかしファイルから読み込むときは一時的に用意した場所を使い回すので中身が変わってしまいますから場所を覚えておいても役に立ちませんし、一時的に用意した場所そのものが無くなることもあります。

誤解されがちですが、ポインタというのは、ポインタ型の変数のことだけを指して使う言葉ではありません。
ポインタ型の変数に格納される値を指してポインタということもあります(これはアドレスと呼ばれることもあります)。
後者のポインタは変数や文字列など実体を持つオブジェクトは必ず持っています。
これをポインタ型の変数に入れて利用するのです。
オブジェクトが持つポインタを理解できてこそきちんと理解できていると言えると思います。
きちんとポインタを理解していれば配列のほうがふさわしいという判断もできるのです。

(追記)
結果表示するとき
list[0].pass
list[0].chip
list[1].pass
list[1].chip
がそれぞれどこを指し示しているか考えてみてください。

beatle
記事: 1281
登録日時: 14年前
住所: 埼玉
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#17

投稿記事 by beatle » 14年前

surf さんが書きました: 今回の最初のエラーの原因を直すため自分の解らない所をちょっとまとめてみました。
List_t型のchar*は初期化されずどこを指してるのか不定大きさも不定な状態でmallocされたのでエラーが起きた。
だからmallocするまでに初期化をしてサイズを決めなければならない。
しかしながらList_t *list;ではList_t型のポインタ変数を宣言しただけなので実体はまだない。
実体はないのにどうやって初期化するのか?
またはchar*不定のままmallocしてとりあえず実体を作りその後初期化でかまわないのか?(なんかこれは間違ってるような気もしますけど・・・・)
以上のように考えて悩んでいるんですが、根本的に何か間違っているのでしょうか?
mallocしてから初期化します.
ここでいう「初期化」は,普通の変数の初期化と違うので,正確には「代入」と言ったほうが誤解が少ないかもしれませんね.
普通の変数の初期化は,(概念的には)変数の実体の確保と同時に値を設定することです.対して今回の場合はmallocで実体を確保してから値を「代入」します.
surf さんが書きました: あと今回のプログラムのようにファイル読み込みをする前の構造体の初期化は

コード:

typedef struct{
    int id;
    char *pass;
    char *chip;
    int x,y;
}List_t;
List_t list[2] = {
    { 4 , "test60x60.csv" , "euro1.png" ,40 ,2  },
    { 5 , "test30x30.csv" , "euro1.png" ,18 ,17 }
};
のように初期化をしていたのですがこれも間違っているのでしょうか?
char*型のポインタ変数に,文字列定数(に限らず,変更不可なデータ)を格納するのは,ちょっと良くないとは思います.まあ,プログラマが注意して扱えばいいのでしょうが.
文字列定数へのポインタ変数ならconst char*型が適しています.

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#18

投稿記事 by softya(ソフト屋) » 14年前

surf さんが書きました:このレスのコードの指定部分にmemset(str,0xcc,sizeof(str));を入れてみた所、意味不明の文字がたくさん表示されました。 今の自分ではこの関数が何をしているのかまったくわかりませんけど、これがISLeさんが仰る本来表示される結果なんですか?
いえ、str配列の内容を書き換えていますのでpassとchipで表示される内容がstr配列の状態に依存していると言うことです。
それとFileRead_gets( str, 256, list_file ) ;  で読み込んでいますので、上書きされて内容が書き換わるはずです。
なので、複数行のデータの場合ちゃんと表示される方が不思議なのです。
surf さんが書きました: 今回の最初のエラーの原因を直すため自分の解らない所をちょっとまとめてみました。
List_t型のchar*は初期化されずどこを指してるのか不定大きさも不定な状態でmallocされたのでエラーが起きた。
だからmallocするまでに初期化をしてサイズを決めなければならない。
しかしながらList_t *list;ではList_t型のポインタ変数を宣言しただけなので実体はまだない。
実体はないのにどうやって初期化するのか?
またはchar*不定のままmallocしてとりあえず実体を作りその後初期化でかまわないのか?(なんかこれは間違ってるような気もしますけど・・・・)
以上のように考えて悩んでいるんですが、根本的に何か間違っているのでしょうか?
mallocされた直後が不定なのはどうしようもないのでmalloc後に自分で値を代入します。
事前に初期化することはできません。ローカル変数をただ書いても初期化されませんよね?
そんな場合はどうすれば良いと思いますか?
例えば
List_t list[2];
だけ書いたらどうでしょうか?
list[0].passの値はどうなっていますか?
後から値を入れるならどうしますか?
surf さんが書きました: あと今回のプログラムのようにファイル読み込みをする前の構造体の初期化は

コード:

typedef struct{
    int id;
    char *pass;
    char *chip;
    int x,y;
}List_t;
List_t list[2] = {
    { 4 , "test60x60.csv" , "euro1.png" ,40 ,2  },
    { 5 , "test30x30.csv" , "euro1.png" ,18 ,17 }
};
のように初期化をしていたのですがこれも間違っているのでしょうか?
メモリ実体を定義して初期値を与えていますので、参照上問題のない形になっています。

うまくイメージできていないと思うので「初心者のためのポイント学習C言語」にある「第10章 ポインタ」のように図を書いてみてはどうしょうか?

[追記]
こういう本を一冊持っているのも良いかも知れません。
「BohYoh.com【著書】詳解C言語ポインタ完全攻略」
http://www.bohyoh.com/Books/ShoukaiCptr/index.html
一部のページが読めます。
http://www.bohyoh.com/Books/ShoukaiCptr/download.html
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

surf

Re: 文字列についての質問とコンパイル時の警告について

#19

投稿記事 by surf » 14年前

ISLeさん、beatleさん、softya(ソフト屋)さん貴重なアドバイスありがとうございます。
ISLe さんが書きました: 最初の質問文にあるcsvデータをこちらで表示してみると

2
4test30x30.csveuro1.png402
5test30x30.csveuro1.png1827

と表示されました。
わたしは

2
4test60x60.csveuro1.png402
5test30x30.csveuro1.png1827

と表示されなければおかしいと思うのですけどsurfさんが予想通りとおっしゃるのならそれで良いのでしょう。
softya(ソフト屋) さんが書きました: いえ、str配列の内容を書き換えていますのでpassとchipで表示される内容がstr配列の状態に依存していると言うことです。
それとFileRead_gets( str, 256, list_file ) ;  で読み込んでいますので、上書きされて内容が書き換わるはずです。
なので、複数行のデータの場合ちゃんと表示される方が不思議なのです。
すみません自分のミスでした。それまで動作すらしなかった物が結果が表示されたのに満足して内容までは細かくみてませんでした。
ISLeさんの仰る通りの結果です。申し訳ありません。
ISLe さんが書きました: (追記)
結果表示するとき
list[0].pass
list[0].chip
list[1].pass
list[1].chip
がそれぞれどこを指し示しているか考えてみてください。
それぞれどこを指し示しているか考えてみました。そしてmemsetが何をしているのかも調べてみました。
そうすると大量のフの意味も原因もわかりました。list[0].passとlist[1].pass、list[0].chipとlist[1].chipはそれぞれstr配列から読み込んだ場所を指していたんですね。
ISLe さんが書きました: 誤解されがちですが、ポインタというのは、ポインタ型の変数のことだけを指して使う言葉ではありません。
ポインタ型の変数に格納される値を指してポインタということもあります(これはアドレスと呼ばれることもあります)。
後者のポインタは変数や文字列など実体を持つオブジェクトは必ず持っています。
これをポインタ型の変数に入れて利用するのです。
今回の失敗と合わせてこの一文を読んだ事によってポインタの理解がかなり前進したように思います。
本当にありがとうございます。
softya(ソフト屋) さんが書きました: 例えば
List_t list[2];
だけ書いたらどうでしょうか?
list[0].passの値はどうなっていますか?
後から値を入れるならどうしますか?
List_t list[2]; と書いただけの初期化の場合は{ }を使っての初期化はできないので直接構造体のメンバーにアクセスして一つ一つ値を代入すると理解してます。
list[0].id = 4;
list[0].pass = "test60x60.csv";
list[0].chip ="euro1.png";
list[0].x = 40;
list[0].y = 2;
list[1].id = 5;
list[1].pass = "test30x30.csv";
 ・
 ・
こんな感じで。
定義時に初期化なしの場合のlist[0].passの値はどこを指しているかわからないという事ですよね?
後から入れる場合は上に書いた方法で行けたのですが、ふと思ってstrcpy(list[0].chip , "euro1.png");で試した所だめでした。この事から察するにstrcpy は euro1.png という実体作ってそのアドレスを渡しているのではなく、list[0].chip の指している場所に euro1.png という実体を作って置き換えているという事ですか?

beatle さんが書きました: mallocしてから初期化します.
ここでいう「初期化」は,普通の変数の初期化と違うので,正確には「代入」と言ったほうが誤解が少ないかもしれませんね.
普通の変数の初期化は,(概念的には)変数の実体の確保と同時に値を設定することです.対して今回の場合はmallocで実体を確保してから値を「代入」します.
softya(ソフト屋) さんが書きました: mallocされた直後が不定なのはどうしようもないのでmalloc後に自分で値を代入します。
beatleさん、softya(ソフト屋)さん、お二方が仰ることを踏まえて自分なりに色々失敗をしながらも動作するコードを書いてみました

コード:

#define _CRT_SECURE_NO_WARNINGS
#include "DxLib.h"
#include <stdlib.h>
#include <string.h>
 
#define PASS_MAX 30
 
typedef struct{
    int id;
    char *pass;     //問題の部分
    char *chip;
    int x,y;
}List_t;
 
int num;
List_t *list;
char a[2][30] , b[2][30];	//追加部分
 
 
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ){
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1;
    SetDrawScreen( DX_SCREEN_BACK ) ;
 
    int i , list_file;
    char str[256];
 
    list_file = FileRead_open("maplist.csv");   
 
    FileRead_gets( str, 256, list_file ) ;
    num = atoi(strtok( str, ",\n" ));           //最初の一行にリストの数が記載されているのでnumに代入
 
    list = (List_t *)malloc(sizeof(List_t) * num);  //リストの数値分メモリを確保
    if (list == NULL) exit(0);
 
    for( i=0 ; i<num ; i++ ){
        list[i].pass = &a[i][0];     //追加部分
        list[i].chip = &b[i][0];
    }
 
    for( i=0 ; i<num ; i++ ){
 
        FileRead_gets( str, 256, list_file ) ;  //一行読み込み
 
        list[i].id = atoi(strtok( str, ",\n" ));
        strcpy(list[i].pass , strtok( NULL , ",\n" ));
        strcpy(list[i].chip , strtok( NULL , ",\n" ));
        list[i].x = atoi(strtok( NULL , ",\n" ));
        list[i].y = atoi(strtok( NULL , ",\n" ));
    }
 
    FileRead_close(list_file);

	memset(str,0xcc,sizeof(str));
 
    printfDx("%d\n",num);           //結果の表示
 
    for( i=0 ; i<num ; i++ ){
        printfDx("%d",list[i].id);
        printfDx("%s",list[i].pass);
        printfDx("%s",list[i].chip);
        printfDx("%d",list[i].x);
        printfDx("%d\n",list[i].y);     
    }
 
 
    free(list);
 
    ScreenFlip() ;
    WaitKey() ;
 
    DxLib_End() ;
    return 0 ;
}
もう一つの案もあります。char型の2次元配列は作らずmallocする方法です。

コード:

for( i=0 ; i<num ; i++ ){
        list[i].pass = (char *)malloc(sizeof(char) * PASS_MAX);
		if (list[i].pass == NULL) exit(0);
        list[i].chip = (char *)malloc(sizeof(char) * PASS_MAX);
		if (list[i].chip == NULL) exit(0);
}
このfor文を先のコードのコメントで追加部分と書いたfor文と入れ替えて下記のコードのfreeを
free(list);の前に加えた形です。

コード:

for( i=0 ; i<num ; i++ ){
        free(list[i].pass);
        free(list[i].chip);
}
これでどうでしょうか?
beatle さんが書きました: char*型のポインタ変数に,文字列定数(に限らず,変更不可なデータ)を格納するのは,ちょっと良くないとは思います.まあ,プログラマが注意して扱えばいいのでしょうが.
文字列定数へのポインタ変数ならconst char*型が適しています.
今までconstについては#defineとほとんど同じような感じがしなくて使い所がわからず使ったことがないのですが、今回のように変数に何が入ってくるかわからず入った値は変えたくない場合に有効なのですね。
教えていただきありがとうございます。今後も使えそうな時があれば積極的に使って行こうと思います。
softya(ソフト屋) さんが書きました: こういう本を一冊持っているのも良いかも知れません。
「BohYoh.com【著書】詳解C言語ポインタ完全攻略」
http://www.bohyoh.com/Books/ShoukaiCptr/index.html
一部のページが読めます。
http://www.bohyoh.com/Books/ShoukaiCptr/download.html
拝見させて頂きました。文だけでは理解しずらい自分にとっては図解入りで説明されているのは解り易そうです。
購入も検討しようと思います。
良書を紹介して頂きありがとうございます。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#20

投稿記事 by softya(ソフト屋) » 14年前

surf さんが書きました:List_t list[2]; と書いただけの初期化の場合は{ }を使っての初期化はできないので直接構造体のメンバーにアクセスして一つ一つ値を代入すると理解してます。
list[0].id = 4;
list[0].pass = "test60x60.csv";
list[0].chip ="euro1.png";
list[0].x = 40;
list[0].y = 2;
list[1].id = 5;
list[1].pass = "test30x30.csv";
 ・
 ・
こんな感じで。
定義時に初期化なしの場合のlist[0].passの値はどこを指しているかわからないという事ですよね?
後から入れる場合は上に書いた方法で行けたのですが、ふと思ってstrcpy(list[0].chip , "euro1.png");で試した所だめでした。この事から察するにstrcpy は euro1.png という実体作ってそのアドレスを渡しているのではなく、list[0].chip の指している場所に euro1.png という実体を作って置き換えているという事ですか?
strcpyは実体を作りません。実体から実体へ内容をコピーするだけです。
strcpyの例ですが次のような内部コードとなっています。
「BohYoh.com【C言語講座】標準ライブラリ strcpy」
http://www.bohyoh.com/CandCPP/C/Library/strcpy.html
すべてのstrcpyがこう書かれているわけではありません。

【追記】
mallocのサイズをPASS_MAXで取る案ですが、それなら配列で書いた方が最適解です。
不定サイズで最小限のサイズを確保したい場合はstrtokで得られた文字列ポインタでstrlenで文字列長を調べてmallocしたあとコピーするとと言う方法もあります。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

surf

Re: 文字列についての質問とコンパイル時の警告について

#21

投稿記事 by surf » 14年前

softya(ソフト屋)さん返信ありがとうございます。
softya(ソフト屋) さんが書きました: strcpyは実体を作りません。実体から実体へ内容をコピーするだけです。
strcpyの例ですが次のような内部コードとなっています。
「BohYoh.com【C言語講座】標準ライブラリ strcpy」
http://www.bohyoh.com/CandCPP/C/Library/strcpy.html
すべてのstrcpyがこう書かれているわけではありません。
strcpyの中身がこんな単純なコードだとは思いもよりませんでした。
終端文字がくるまでコピー先に代入しているだけだったんですね。
softya(ソフト屋) さんが書きました: mallocのサイズをPASS_MAXで取る案ですが、それなら配列で書いた方が最適解です。
不定サイズで最小限のサイズを確保したい場合はstrtokで得られた文字列ポインタでstrlenで文字列長を調べてmallocしたあとコピーするとと言う方法もあります。
勉強の為にも作ってみました。

コード:

for( i=0 ; i<num ; i++ ){
 
        FileRead_gets( str, 256, list_file ) ;  //一行読み込み
 
        list[i].id = atoi(strtok( str, ",\n" ));

	    strcpy(temp , strtok( NULL , ",\n" ));
	    list[i].pass = (char *)malloc(sizeof(char) * (strlen(temp)+1));
	    if (list[i].pass == NULL) exit(0);
        strcpy(list[i].pass , temp);

	    strcpy(temp , strtok( NULL , ",\n" ));
	    list[i].chip = (char *)malloc(sizeof(char) * (strlen(temp)+1));
        if (list[i].chip == NULL) exit(0);
        strcpy(list[i].chip , temp);

        list[i].x = atoi(strtok( NULL , ",\n" ));
        list[i].y = atoi(strtok( NULL , ",\n" ));
}
読み込み部分をこんな感じに変えてみました。一時的に文字を格納する文字配列tempを上の方で定義してあります。
CSVファイルの文字列の長さを変えたり、後からmemsetでtempの中身を書き換えてもきちんと動作するのでたぶん
大丈夫だとは思います。


今回の失敗に対して様々なアドバイスを頂いたおかげで最初のエラーの原因
strcpy(list.pass , strtok( NULL , ",\n" ));がポインタ不定です。 の意味もすべて理解できたと思います。
一応これで解決とさせて頂こうと思います。

これまで貴重な助言を下さった方々ほんとうにありがとう御座いました。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 15年前
住所: 東海地方
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#22

投稿記事 by softya(ソフト屋) » 14年前

strcpy(temp , strtok( NULL , ",\n" ));
だと不定サイズとは言えないので、
char *pStr;
を定義して
pStr = strtok( NULL , ",\n" );
でstrlen(pStr)などをすれば余分なtempバッファは必要ありません。

[補足]
まぁ、
FileRead_gets( str, 256, list_file ) ; //一行読み込み
で256バイトに制限されているのでtempがそれを超える事は無いのでバッファサイズは制限があるのはあるのですが。
可変長に拘るなら出来るだけ固定サイズのバッファは持たないほうが良いとは思います。

[余談]
何でもかんでもmallocするとその分メモリリークのリスクも高くなるので、そのリスクを避けるためにISLeさんが固定サイズバッファを推奨している訳です。
mallocするのか固定サイズ配列を使うのかは、うまく使い分けをして下さい。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

ISLe
記事: 2650
登録日時: 15年前
連絡を取る:

Re: 文字列についての質問とコンパイル時の警告について

#23

投稿記事 by ISLe » 14年前

pass,chipメンバを配列にするとList_t分のメモリ確保一回で済みますからね。

構造体のメンバの配列の先頭要素を指すポインタってのも理解してほしいと思います。

surf

Re: 文字列についての質問とコンパイル時の警告について

#24

投稿記事 by surf » 14年前

softya(ソフト屋)さん、ISLeさん返信ありがとうございます。
最後まで為になるアドバイス頂いて感謝してます。

ファイル読み込みプログラムを実装する時はお二方が仰る通り配列で確保する方法で行こうと思います。

今回自分の質問に回答して頂いたみなさまありがとうございました。
独学で学んでいる自分にとってはすべての回答が為になる物ばかりでした。
またわからない事があれば、その時はまたよろしくお願いします。

閉鎖

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