• ベストアンサー

文字列、ポインタを使用したプログラムで分からないところがあります

1 #include <stdio.h> 2 3 void setstr(char *str){ 4   str = "abc"; 5   return; 6 } 7 8 int main(){ 9 10   char *str; 11 12   setstr(str); 13 14   str[1] = 'E'; 15 16   printf("str = %s\n", str); 17 18   return 0; 19 } 20 上記のプログラムの動きがいまいち理解できません。 (メモリの状態など) 16行目でprintfすると、結果は「str = 、E・」となります。 ---まず、4行目でabcに対してメモリが確保されて、その先頭アドレスが strに設定されます。 しかし、setstr関数を抜けた時点で、先ほど確保されたメモリは開放されて しまう。(? ここは想像です。確証がありません) main関数に戻ってきて、14行目で変更しているメモリは、abcがかつてあった 場所の"b"の部分。(str自体は何も変更されていないから) 16行目でprintfしているのだけど、なぜこの結果になるのかが分かりません。。 分かる方いましたら教えて下さい。上の文章では何を言っているのか分かりづらいとは 思いますが。。 説明には適宜行番号を使って頂いて構いません。 よろしくお願いします。

質問者が選んだベストアンサー

  • ベストアンサー
  • osamuy
  • ベストアンサー率42% (1231/2878)
回答No.4

デバッガgdbを使って、実際にどのアドレスを指すかを実験(後述)してみると、main()で定義している変数str($2で指示されるアドレス)と、setstr()の仮引数str($4で指示されるアドレス)は別物であることがわかります。 これは、cパラメータ受け渡し方がcall by valueである事によるものです。 なので、setstr()で文字列のアドレスを設定したと思っていても、main()のstrには影響してません(setstr()から戻った直後の$7の値をみると明らか)。 こんな風にデバッガで追っかけたり、アセンブラソース出力(gcc -Sとかcl.exe /FAとかで生成)を眺めてみると、感じがつかめると思います。 (gdb) list 1 void setstr( char *str ){ 2 str = "abc"; 3 return; 4 } 5 int main(){ 6 char *str; 7 setstr( str ); 8 str[1] = 'E'; 9 return 0; 10 } (gdb) b 7 Breakpoint 1 at 0x401ada: file a.c, line 7. (gdb) run Starting program: a.out Breakpoint 1, main () at a.c:7 7 setstr( str ); (gdb) p str $1 = 0x0 (gdb) p &str $2 = (char **) 0x81ff88 (gdb) step setstr (str=0x0) at a.c:2 2 str = "abc"; (gdb) p str $3 = 0x0 (gdb) p &str $4 = (char **) 0x81ff70 (gdb) n 4 } (gdb) p str $5 = 0x405048 "abc" (gdb) p &str $6 = (char **) 0x81ff70 (gdb) n main () at a.c:8 8 str[1] = 'E'; (gdb) p str $7 = 0x0 (gdb) p &str $8 = (char **) 0x81ff88 (gdb) p &str[1] $9 = 0x1 <Address 0x1 out of bounds> (gdb) n Program received signal SIGSEGV, Segmentation fault. 0x00401aec in main () at a.c:8 8 str[1] = 'E'; (gdb) quit %

upanepa
質問者

お礼

ご回答ありがとうございます。 回答者さんの環境では、セグメンテーションフォールトが おきるのですね。私の環境(BCC)では、おきませんでした。 これは置いておいて・・・ 指摘された内容を見て、値渡しがダメなので参照渡しにして みたら期待した動作になりました。 void setstr(char *str){ ↓ void setstr(char *&str){ よく考えてみたら、4行目で、str自身を書き換えてますね。。 動作するようにはなりましたが、setstr内で登場した"abc"の部分の メモリの扱いはどのようなものなるのかが謎です。 mainに制御が戻った後で、自然にこの部分が上書きされて しまわないのかとか。。mallocで領域を確保したわけじゃないしなあ、とか。。 そもそもこのプログラムの書き方はおかしいのかな。。

その他の回答 (12)

  • andy_kun
  • ベストアンサー率23% (64/274)
回答No.13

>>ANo12 プロセスが終了した時に確実にメモリが解放されるならメモリリークなんて起きないでしょ。 実際AmigaOSはプロセスが終了しても確保したメモリは自動では解放されません。

回答No.12

>処理系および実装に依存 言ってる意味がわかりません。 解放されない処理系とは? 解放されない実装とは?

  • andy_kun
  • ベストアンサー率23% (64/274)
回答No.11

ANo2.ANo6.です。 mallocで確保したメモリをプログラム終了時に解放するのは処理系および実装に依存します。 実際に解放しない処理系も存在します。 質問に処理系が指定されていない以上freeは必要です。

upanepa
質問者

お礼

そうなんですね、それは知りませんでした。 自分自身はどこでも使用可能なソースの方が よいので、freeは付けるようにしようと思います。

  • koko_u_
  • ベストアンサー率18% (459/2509)
回答No.10

>同一スコープで確保・開放されることが >正しいということでしょうか。 正しいというか、その位に抑えておかないと私はメンテナンスできない。 >メモリの確保・開放は関数をまたいで、 >どこでもできるので、このような方針が >あることは知りませんでした。 どこでもできる、ということは裏を返せばどこでやっているのか すぐにわからなくなるということです。

upanepa
質問者

お礼

なるほど!同一スコープで確保/開放するよう 気をつけようと思います

回答No.9

No.5ですが、 mallocによってヒープ領域に確保されたメモリはプログラム終了時に解放されます。 よって"このプログラムにおいては"解放は不要です。

upanepa
質問者

お礼

確か、main関数は、明示的に開放しなくても、 プログラムが終了するタイミングで、確保していた メモリは開放するんですよね

回答No.8

想像ですが・・・ 10行目で宣言されたstrはポインタですので、アドレスを入れる箱は準備されますが、その中身は「どこだかわからないアドレス」です。 普通はここで新しく領域を確保してちゃんとしたアドレスを入れます。 12行目でstrstr関数を呼ぶと、まずstrの値(どこだかわからないアドレス)をスタックにコピーしてstrstrの本体を実行します。 3行目のstrstrは、スタックからstrの中身(どこだか分からないアドレス)を作業メモリにコピーします。(コピーしないかもしれませんが話は同じです) 4行目では、"abc"が格納されているアドレスが作業メモリに上書きされます。 5行目のreturnは、strstr関数の返値がvoidなので何もせずに呼ばれたプログラムに戻ります。 14行目ではstrの中身(どこだか分からないアドレス)の次のアドレスに'E'を格納します。(ここでOSが止まったりします) 参照渡しだとstrのアドレスを渡すので一応動くと思いますが、"abc"が格納されている領域が書き換えできないような場所だったりするとまずいです。

upanepa
質問者

補足

ご回答ありがとうございます。 > 14行目ではstrの中身(どこだか分からないアドレス)の次の~~ ここまではあれからいろいろ考えて私も同じことを考えました。 最後の一文についてですが、 > "abc"が格納されている領域が書き換えできないような場所 上記のような場所に領域が確保されることはあるのでしょうか? 現状では、書き換え可能な領域にメモリが確保されているようです。 このプログラムのように、setstr関数の中で、 メモリを確保しないプログラムでも、そもそもご法度な書き方 なのでしょうか。もしそうなると下記のプログラムも 不正ということになるんでしょうか? (動作確認済みです。一応問題なく動作しました) 文字列・ポインタ辺りは、文法が難しいです^^; #include <stdio.h> int main(){ char *str; str = "abc"; str[1] = 'B'; printf("str = %s", str); return 0; }

  • koko_u_
  • ベストアンサー率18% (459/2509)
回答No.7

例によって他の人の回答は読んでませんが、 普通 setstr() のような関数を作って、文字列を格納する処理を作成する場合、 格納するための場所は関数を呼び出す側、この場合では main で確保するのが普通です。 setstr() のプロトタイプ宣言は質問文にあるままで結構です。 呼び出し側で適切に領域が確保されていることを前提として、その先頭アドレスを引き数にもらうのは普通の設計です。 setstr() の中で領域を確保して、呼び出し側でその領域を開放させるのは悪い設計です。 普通のプログラマは現在のスコープで確保した領域のことを覚えていたとしても、setstr() の中で確保される領域のことまでは気にしません。

upanepa
質問者

お礼

ご回答ありがとうございます。 同一スコープで確保・開放されることが 正しいということでしょうか。 メモリの確保・開放は関数をまたいで、 どこでもできるので、このような方針が あることは知りませんでした。

  • andy_kun
  • ベストアンサー率23% (64/274)
回答No.6

再度ANo.2です。 ANo.5さんのプログラムで概ね良いですが80点ですね。 setstr関数で確保したメモリはプログラム終了前に解放する必要があります。 アロケートしたメモリを解放しないのは重大なバグに繋がりますから、注意が必要ですね。 ポインタはC言語を難解にするところ?ですから、もう少し勉強したほうが良いですよ。

upanepa
質問者

お礼

確かにfreeが必要っぽいですね 自分もよくやってしまいます^^;

回答No.5

結果としては str = aEc を期待していたわけですよね? それを実現するには以下のコードで可能です。 1 #include <stdio.h> 2 #include <stdlib.h> 3 4 void setstr(char **str){ 5 *str = malloc(4); 6 sprintf(*str, "abc"); 7 return; 8 } 9 10 int main(){ 11 12 char *str; 13 14 setstr(&str); 15 16 str[1] = 'E'; 17 18 printf("str = %s\n", str); 19 20 return 0; 21 }

upanepa
質問者

補足

ご回答ありがとうございます。 解放の話はそれはそうとして、、 3 void setstr(char *&str){ 4   str = "abc"; 5   return; 6 } 上記のような文字列の設定の仕方は認められていないのでしょうか? 一応期待した動作になります。 自分でもあんまりみたことありませんが。。

  • andy_kun
  • ベストアンサー率23% (64/274)
回答No.3

ANo2です。 追記です。C言語は値渡しですから呼び出し先で引数(この場合はstr)に値を入れても、呼び出し元ではstrの内容は変更されていません。 void main() { char *str; str = 0x00; func(str); } void func(char *str) { str=0x01; } こう書いてもfuncの呼出し後のstrの値は0です。 void main() { char *str; str = 0x00;   *str = 0x30; func(str); } void func(char *str) { *str=0x31; } これであればstrの指し示している内容は0x31に変更されます。

upanepa
質問者

お礼

ご回答ありがとうございます。 ポインタ自身は、変更することができないが、 ポインタが指している先は変更することができるという ことかな。。

関連するQ&A

  • mainから渡した文字列を関数内で書き換え

    非常に基礎的な質問で申し訳ないのですが mainから渡した文字列を関数内で書き換えることができません。 int型の整数やchar型一文字はできるのですが。。。 例えば以下のようなソースでmainのABCをDEFに書き換えたいとき どのようにすればいいのでしょうか。 (関数の戻り値で変更という方法以外で) 以下のソースでは値は書き換わりませんでした。 void func(char *str2) { str2 = "DEF"; } int main() { char str1[20] = "ABC" printf("%s", str1); //ABC func(str1); printf("%s", str1); //DEFになるようにしたい }

  • ポインタで分からないことがあります。

    つい最近C言語の勉強を始め、現在ポインタの勉強をしています。 過去の質問を検索したり、サイトを見てみましたが、一人の力では解決できませんでしたので質問させていただきます。 ポインタのプログラムで、下記のプログラムについて分からないことがありました。 ――――――――――――――――――――――――――――――――― #include <stdio.h> int main (void) { char *str = "abc"; printf ("%s %d %d\n", str, &str, &(*str)); str = "日本語"; printf ("%s %d %d\n", str, &str, &(*str)); return 0; } ――――――――――――――――――――――――――――――――― このプログラムで、「char *str = "abc";」の部分でstrには abcのアドレスが入っていると思っていたのですが、 1度目の「printf ("%s %d %d\n", str, &str, &(*str));」で、 結果が「abc 1245064 4235560」となっているのを見ると 私の見解は間違っている気がします。 「char *str = "abc";」の部分では一体なにが行われているのでしょうか? また、このプログラムをコンパイルして実行した結果が、 abc 1245064 4235560 日本語 1245064 4235574 となったのですが、なぜstrのアドレスは同じなのに、 &(*str)のアドレスは異なるのでしょうか? 質問をまとめますと、以下の2つです。 1.「char *str = "abc";」の部分では一体なにが行われているのでしょうか? 2.「abc」と「日本語」のstrのアドレスは同じなのに、&(*str)のアドレスは異なるのでしょうか? 初心者ですので言葉の足らない部分があるかもしれませんが、ご教授のほどよろしくおねがいします。

  • abcが、入力された文字列内にあるかどうかを表示するプログラム

    文字列strの中にabcが含まれていれば、1を返し、含まれていなければ0を返すプログラムが分かりません。 C言語の問題で下記のものが分かりません。どなたか知恵を貸してください。 ユーザが文字を入力し、CTRL+Zが押されるまで、半角英数字の入力(最大でも1000文字)を受け付ける。文字列「abc」が、入力された文字列内にあるかどうかを表示するプログラムを作成する。ユーザが入力した文字列が3文字未満はabcがありませんと表示させる。 そのプログラム内で以下の関数を完成させる。 int str_srch_abc(char str []) 文字列strの中にabcが含まれていれば、1を返し、含まれていなければ0を返す関数とする。 (例えばabcは連続でabcの時だけ1を返し、asbscなどはoを返します。) ちなみに自分なりにやってみたのですが、ここまでしかできませんでした。 #include<stdio.h> int main() { int str_srch_abc(char str []); char str[1000]; int ch=0, j=0; printf("半角英数字を入力してください"); scanf("%s",str); while((ch=getchar())!=EOF){ str[j]=ch; j++; } str[j]='\0'; printf("%s",str); return(0); }

  • 文字列の扱い方

    初歩的な質問ですみません… str文字列からcという文字を見つけたら添字を返すという関数を作ったのですが、 iにこの関数を代入して、if文の制御式にiを使って比較するまでは正常なのですが、 真文にiを使うと何故か偽文(という言い方でいいのでしょうか…この場合("そんな値はありません。"というところです)が実行されてしまいます。 よろしければご教授お願い致します。 #include <stdio.h> int str_char(const char str[],int c) { int len = strlen(str); int i; for (i = 0;i < len;i++) { if (str[i] == c) return i; } return -1; } int main() { char str[64] = "Fucking Brutal Death Metal"; int ch,i; printf("どの文字を調べますか?"); scanf("%c",&ch); i = str_char(str,ch); if (i >= 0) printf("その文字は%d番目にあります。",str_char(str,ch) + 1); //何故かiだと動かない else printf("そんな値はありません。"); return 0; }

  • プログラミング ポインタを使った文字列比較

    プログラミング ポインタを使った文字列比較 2つの文字列str1, str2を入力し,それらが等しければ0,等しくなければ1を返す関数str_compareを作り、返り値によって以下のように表示するプログラムを作れ。ただし,関数strcmpを使ってはならない。 文字列の入出力はmain関数で行い,関数str_compareの仮引数にはポインタ変数を宣言し,ポインタと間接演算子*を用いた処理を行うこと。 % ./a.out input str1 = Worldcup input str2 = Worldcup same strings % ./a.out input str1 = World input str2 = cup different strings この問題に私は次のようにプログラミングしました。 #include <stdio.h> #define MAX 100 int str_compare(char *, char *); main() { char str1[MAX], str2[MAX]; printf("input str1 = %s", str1); scanf("%s", str1); printf("input str2 = %s", str2); scanf("%s", str2); str_compare(str1, str2); if (str_compare(str1, str2) == 0) printf("same strings\n"); else if (str_compare(str1, str2) == 1) printf("different strings\n"); } int str_compare(char *s1, char *s2) { int i; for (i = 0; s1[i] != '\0'; i++) { if (s1[i] != s2[i]) { break; } } if (s1[i] == s2[i]) { return 0; } else { return 1; } } これで実行したところ、「input str1 =」の右のスペースが文字化け?してしまいます。(半角カタカナや記号が出る)ただ、その後に文字列を入力すると、正しく機能します。 これは何が悪いなのでしょうか、どなたか教えてください。

  • 文字列中に含まれる文字の個数をカウントするプログラムについて…

    文字列、1文字が与えられたとき、これをポインタで入力し文字列中に含まれる文字の個数を計算するプログラムを作成せよ。 と、いう課題がだされたんですけど、ユーザが任意の文字列と1文字を入力できるようにすることができません…。 多分main関数の部分をちょっといじくればよいと思うのですが…。 どなたかアドバイスをお願いします。 #include <stdio.h> int count(const char *str, const char ch) { int cnt=0; while (*str!='\0') { if (*str==ch) cnt++; str++; } return cnt; } int main() { const char *str="hello,world!"; const ch='o'; int cnt; cnt=count(str, ch); printf("%s中に%cは%d個です\n", str, ch, cnt); return 0; }

  • 文字列とポインタの問題です。

    #include<stdio.h> int f(char *s); int main(void){ char*str="nasida Institute of Technology"; int i; i=f(str); printf("%s:%d\n",str,i); return 0; } int f(char *s) { int j=0; while(*s!='\0'){ if(*s=='t'){ j++; } s++; } return j; } このプログラムの答えが3になるんですが、if文のとこの動作がよく分からないので、よろしくお願いします。

  • 配列やポインタに文字列を設定することについて

    ◎1------------------------- #include<stdio.h> int main(void) { char ss[80]; scanf("%s",ss); printf("%s\n",ss); return 0; } ---------------------------- ◎2--------------------------- #include<stdio.h> int main(void) { char *ss="abcde"; printf("%s\n",ss); return 0; } ------------------------- ◎3---------------------- #include<stdio.h> int main(void) { char *ss; ss="abcde"; printf("%s\n",ss); return 0; } ------------------------- 以上3つプログラムで疑問をいだいたのですが、 まず◎1で、これは例えば、 cahr ss[80]="abc"; のように配列ssに文字列"abc"そのものを入れているのか、 char *ss="xyz"; のようにまず"xyz"という文字列をメモリ上のどこかに設定し、その先頭番地をssに代入しているのか、どちらの考えでいいのかわかりません。 次に、◎2、3ではどちらも正常に実行できたのですが、特に◎3で「ss="abcde";」と記述していますが、ssにはアドレスを代入するという認識かあるのですが、文字列定数を代入しても問題ないのか?という疑問があります。 教えていただけたら嬉しいです。

  • ポインタによる文字列の出力

    こんにちは、お世話になっています。 この参考書のプログラムの例題のソースを実行しようとしたのですが、 Visual Studioを使っているのですが、警告がなぜか出てしまいます。 プログラム力もそんなにないので何が原因か解りません。 ご教示お願いします。 #include<iostream> using namespace std; int main() { char* str = "Hello"; ←ここでエラー cout << str << '\n'; return 0; } 重大度レベル コード 説明 プロジェクト ファイル 行 抑制状態 エラー (アクティブ) E0144 型 "const char *" の値を使用して型 "char *" のエンティティを初期化することはできません

  • 文字列ポインタとgets関数の関係について。

    以下のプログラムはコンパイルは出来ますが、 実行するとクラッシュしてしまいます。 gets関数は char *gets( char *str ); と定義されているので文字列の先頭アドレスを返すはずですが 何故このプログラムはエラーが出るのでしょうか・・。 #include <stdio.h> int main ( void ){  char *p, *s;  p = gets(s);  printf("%s", p);  return 0; }