アプリ版:「スタンプのみでお礼する」機能のリリースについて

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しているのだけど、なぜこの結果になるのかが分かりません。。

分かる方いましたら教えて下さい。上の文章では何を言っているのか分かりづらいとは
思いますが。。
説明には適宜行番号を使って頂いて構いません。
よろしくお願いします。

A 回答 (13件中1~10件)

デバッガ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
%
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。
回答者さんの環境では、セグメンテーションフォールトが
おきるのですね。私の環境(BCC)では、おきませんでした。
これは置いておいて・・・

指摘された内容を見て、値渡しがダメなので参照渡しにして
みたら期待した動作になりました。

void setstr(char *str){

void setstr(char *&str){

よく考えてみたら、4行目で、str自身を書き換えてますね。。

動作するようにはなりましたが、setstr内で登場した"abc"の部分の
メモリの扱いはどのようなものなるのかが謎です。
mainに制御が戻った後で、自然にこの部分が上書きされて
しまわないのかとか。。mallocで領域を確保したわけじゃないしなあ、とか。。
そもそもこのプログラムの書き方はおかしいのかな。。

お礼日時:2008/06/21 17:39

>>ANo12


プロセスが終了した時に確実にメモリが解放されるならメモリリークなんて起きないでしょ。

実際AmigaOSはプロセスが終了しても確保したメモリは自動では解放されません。
    • good
    • 0

>処理系および実装に依存


言ってる意味がわかりません。
解放されない処理系とは?
解放されない実装とは?
    • good
    • 0

ANo2.ANo6.です。


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

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

お礼日時:2008/07/20 00:20

>同一スコープで確保・開放されることが


>正しいということでしょうか。
正しいというか、その位に抑えておかないと私はメンテナンスできない。

>メモリの確保・開放は関数をまたいで、
>どこでもできるので、このような方針が
>あることは知りませんでした。
どこでもできる、ということは裏を返せばどこでやっているのか
すぐにわからなくなるということです。
    • good
    • 0
この回答へのお礼

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

お礼日時:2008/07/20 00:18

No.5ですが、


mallocによってヒープ領域に確保されたメモリはプログラム終了時に解放されます。
よって"このプログラムにおいては"解放は不要です。
    • good
    • 0
この回答へのお礼

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

お礼日時:2008/07/09 22:21

想像ですが・・・



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

参照渡しだとstrのアドレスを渡すので一応動くと思いますが、"abc"が格納されている領域が書き換えできないような場所だったりするとまずいです。

この回答への補足

ご回答ありがとうございます。

> 14行目ではstrの中身(どこだか分からないアドレス)の次の~~
ここまではあれからいろいろ考えて私も同じことを考えました。

最後の一文についてですが、
> "abc"が格納されている領域が書き換えできないような場所
上記のような場所に領域が確保されることはあるのでしょうか?
現状では、書き換え可能な領域にメモリが確保されているようです。

このプログラムのように、setstr関数の中で、
メモリを確保しないプログラムでも、そもそもご法度な書き方
なのでしょうか。もしそうなると下記のプログラムも
不正ということになるんでしょうか?
(動作確認済みです。一応問題なく動作しました)

文字列・ポインタ辺りは、文法が難しいです^^;

#include <stdio.h>

int main(){
char *str;
str = "abc";
str[1] = 'B';
printf("str = %s", str);
return 0;
}

補足日時:2008/07/09 22:37
    • good
    • 0

例によって他の人の回答は読んでませんが、


普通 setstr() のような関数を作って、文字列を格納する処理を作成する場合、
格納するための場所は関数を呼び出す側、この場合では main で確保するのが普通です。

setstr() のプロトタイプ宣言は質問文にあるままで結構です。
呼び出し側で適切に領域が確保されていることを前提として、その先頭アドレスを引き数にもらうのは普通の設計です。

setstr() の中で領域を確保して、呼び出し側でその領域を開放させるのは悪い設計です。
普通のプログラマは現在のスコープで確保した領域のことを覚えていたとしても、setstr() の中で確保される領域のことまでは気にしません。
    • good
    • 0
この回答へのお礼

ご回答ありがとうございます。

同一スコープで確保・開放されることが
正しいということでしょうか。

メモリの確保・開放は関数をまたいで、
どこでもできるので、このような方針が
あることは知りませんでした。

お礼日時:2008/07/09 22:15

再度ANo.2です。


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

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

お礼日時:2008/06/21 17:46

結果としては


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 }

この回答への補足

ご回答ありがとうございます。
解放の話はそれはそうとして、、

3 void setstr(char *&str){
4   str = "abc";
5   return;
6 }

上記のような文字列の設定の仕方は認められていないのでしょうか?
一応期待した動作になります。
自分でもあんまりみたことありませんが。。

補足日時:2008/06/21 17:43
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!