none
C言語における文字列の扱い RRS feed

  • 質問

  • C++ではなく、純粋なC言語について質問です。

    C言語で文字列を返すような関数を作る場合、
    呼び出し側で予め領域を確保しておくことが通例と思います。(パターン1)

    // 呼び出される関数
    int getString(char *str){
      wsprintf(str, "test");
    }
    // 呼び出す側
    char  str[100];
    ZeroMemory(str, sizeof(str));
    getString(str);

    これを改変して関数の戻り値で文字列を得るようにしたとします。(パターン2)

    // 呼び出される関数
    LPSTR  getString(){
      return("test");
    }
    // 呼び出す側
    LPSTR	str;
    str = getString();

    これについて
    ①パターン1では呼び出す側の関数が終了すると文字列は解放されるが、
     パターン2は呼び出す側の関数が終了しても文字列が解放されないと聞かされました。本当でしょうか。

    ②仮にパターン2で文字列が解放されないとした場合、どのようにすれば解放されますでしょうか。

    ③パターン2のような書き方をした場合、解放されないこと以外にどのような問題を生じる可能性があるでしょうか。

    なかなかこういった細かい話は調べてもはっきりとした解答が見つけられず…

    よろしくお願い致します。


    2013年8月12日 11:01

回答

  • ①パターン1では呼び出す側の関数が終了すると文字列は解放されるが、
     パターン2は呼び出す側の関数が終了しても文字列が解放されないと聞かされました。本当でしょうか。

    固定の文字列がメモリ上に存在しており、常に再利用される(毎回確保・コピーされるわけではない)から、解放されないのでしょう。
    この動きは C の言語仕様として規定されているはずです。(が、根拠として示せる文献を手元に持っていないので C 言語の規格・仕様を調べてみてください)

    // 自信はないけど、パターン1もパターン2も文字列リテラルは常に存在するので、「パターン1で解放される」という話が怪しいと思います。

    ②仮にパターン2で文字列が解放されないとした場合、どのようにすれば解放されますでしょうか。

    する必要はありません。

    ③パターン2のような書き方をした場合、解放されないこと以外にどのような問題を生じる可能性があるでしょうか。

    リテラル(定数的な)文字列であれば問題ないかもしれませんが、動的に文字列を生成するようになった途端に不安定なコードになるのでやめた方がよいでしょう。
    LPSTR を返す関数だが、この関数の戻り値は解放しなければならない(動的に確保された)のか、解放してはいけない(静的に確保された文字列リテラルな)のか、それを関数のシグネチャからは判断できません。従って、関数を利用する立場の人は、毎回実装の詳細を調べねばならず、生産性を落とす(実装効率が悪くなる)要因になるでしょう。

    2013年8月12日 11:18
    モデレータ
  • スタックの情報はその関数を抜けた時点で解放されると理解しているのですが(そもそも私のその理解も正しいのか疑わしいのですが)
    この場合、

    『getStringを抜けた時点でvalueは解放されているが、メモリ上の情報が消去されるわけではないので呼び出した側でもvalueの値が読めているだけ。はされているのでどこかのタイミングで書き換えられる可能性がある』

    という理解であっているでしょうか。

    このような処理を書いた場合の動作は未定義だと思っています。
    読めることがあったとしても、いつでも読めるわけではありませんし、場合によってはアクセス保護違反として落ちる可能性もあります。
    従って、こんな関数は作ってはいけません。

    2013年8月14日 10:55
    モデレータ

すべての返信

  • ①パターン1では呼び出す側の関数が終了すると文字列は解放されるが、
     パターン2は呼び出す側の関数が終了しても文字列が解放されないと聞かされました。本当でしょうか。

    固定の文字列がメモリ上に存在しており、常に再利用される(毎回確保・コピーされるわけではない)から、解放されないのでしょう。
    この動きは C の言語仕様として規定されているはずです。(が、根拠として示せる文献を手元に持っていないので C 言語の規格・仕様を調べてみてください)

    // 自信はないけど、パターン1もパターン2も文字列リテラルは常に存在するので、「パターン1で解放される」という話が怪しいと思います。

    ②仮にパターン2で文字列が解放されないとした場合、どのようにすれば解放されますでしょうか。

    する必要はありません。

    ③パターン2のような書き方をした場合、解放されないこと以外にどのような問題を生じる可能性があるでしょうか。

    リテラル(定数的な)文字列であれば問題ないかもしれませんが、動的に文字列を生成するようになった途端に不安定なコードになるのでやめた方がよいでしょう。
    LPSTR を返す関数だが、この関数の戻り値は解放しなければならない(動的に確保された)のか、解放してはいけない(静的に確保された文字列リテラルな)のか、それを関数のシグネチャからは判断できません。従って、関数を利用する立場の人は、毎回実装の詳細を調べねばならず、生産性を落とす(実装効率が悪くなる)要因になるでしょう。

    2013年8月12日 11:18
    モデレータ
  •  呼び出し側が確保する場合は、

    1. 必要量(最大量)が決まっている
    2. 同じ関数で引数を調整すること、または専用の関数によって、確保量を求める

    ことが多いです。GetPrinter 関数などが参考になるでしょうか。

     パターン①で、「関数が終わると解放される」というのは、どのメモリでしょうが。呼び出される側の「"test"」は、プログラム中に確保されるため、解放されません。バイナリエディタなどで実行ファイルを見ることで確認できます。呼び出し側の「str」変数のことであるなら、これは自動変数なので、解放されます。

     パターン②は、上記のことと同じで、解放することはできません。ここが、「return strdup("test");」等となっているなら、解放“しなければならない”のですが。

     Azuleanさんも書かれていますが、呼び出された側で確保することは、あまり勧めません。たとえば、FormarMessage 関数のように、呼び出し側が「そっちで確保してくれ」と指示するようにしてあるなら、また別ですが。strtok 関数は、文字列を返しますが、これは元の文字列を破壊して、元の文字列の一部のポインタを返します。こういうのは、最悪です。


    Jitta@わんくま同盟

    2013年8月13日 1:10
  • 厳格に言うと、C言語にもC++にも、文字列という型は存在しません。CやC++では、「char(Windowsでは、8bit)、または wchar_t(コンパイラによっては unsigned short。Windows では16bit)の配列で、最後が\0で終わるデータの集合体を文字列とする(超絶意訳モード)」と言語規格で規定されています。

    配列自体はわかりますよね?

    ちなみに、C++の文字列型は、ライブラリに定義されているユーザー定義型の一つです。クラスの実装は、上述した文字列データを扱いやすく取りまとめているにすぎません。

    さて、本題。

    パターン2を簡素に書き直すと

    LPSTR str;
    str = "test";
    

    となります。

    この形で表現されていたら領域解放の問題があるかどうかがわかりますか?


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2013年8月13日 3:17
  • ご回答ありがとうございます。また、返信が遅くなりまして申し訳ありません。

    固定文字列に対するCの言語仕様、知りませんでした。
    また、戻り値で文字列を返さない方が良いという理由もよく分かりましたが、まだ少し不明な点があるため加えて質問させてください。

    質問内容を完結にするため、コードを最小限にしておりましたが
    質問のgetString関数を固定文字列のような静的な情報を返す関数ではなくGetPrivateProfileString関数を使用してiniファイルから取得した値を返すようにした場合はどうなるのでしょうか。

    // ※バッファサイズやセクション名などの定義を記述していませんが、本質問と関係ないため省略しています。
    LPTSTR  getString(){
     TCHAR value[BUFFER_SIZE];
     ZeroMemory(value, sizeof(value));
     GetPrivateProfileString(SECTION_NAME, KEY_NAME, _T(""), value, sizeof(value), FILE_NAME);
     Return value;
    }

    mallocのように動的に取得したヒープはFreeで解放しなければならないということは分かりますが
    変数宣言時に静的に取得しているスタックに対しては?となって質問させていただいた次第です。

    スタックの情報はその関数を抜けた時点で解放されると理解しているのですが(そもそも私のその理解も正しいのか疑わしいのですが)
    この場合、

    『getStringを抜けた時点でvalueは解放されているが、メモリ上の情報が消去されるわけではないので呼び出した側でもvalueの値が読めているだけ。すでに解放はされているのでどこかのタイミングで書き換えられる可能性がある』

    という理解であっているでしょうか。

    以上、よろしくお願い致します。



    2013年8月14日 10:50
  • スタックの情報はその関数を抜けた時点で解放されると理解しているのですが(そもそも私のその理解も正しいのか疑わしいのですが)
    この場合、

    『getStringを抜けた時点でvalueは解放されているが、メモリ上の情報が消去されるわけではないので呼び出した側でもvalueの値が読めているだけ。はされているのでどこかのタイミングで書き換えられる可能性がある』

    という理解であっているでしょうか。

    このような処理を書いた場合の動作は未定義だと思っています。
    読めることがあったとしても、いつでも読めるわけではありませんし、場合によってはアクセス保護違反として落ちる可能性もあります。
    従って、こんな関数は作ってはいけません。

    2013年8月14日 10:55
    モデレータ
  • よくわかりました。

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


    2013年8月14日 11:14
  • > 『getStringを抜けた時点でvalueは解放されているが、

    これが理解できているなら、よく考えれば答にたどり着けるはずです。

    解放されているということは、value変数が使用していたメモリはもう使ってな
    いから好きに利用して良いよ。ということです。
    たとえば、以下のようなコードであったとしても、
     void main()
     {
       printf("%s", getString());
     }

    printf()関数も立派なC言語関数の一つです。
    printf()関数の中でローカル変数を使用していれば、そのローカル変数がvalue
    変数の使用していたメモリ領域を再利用する(つまり旧value変数の値はローカル
    変数の値で上書きされる)可能性があります。

    そうなれば、printf関数内の %s を展開する処理中ではすでに上書き済みのメモ
    リ領域を参照することになります。
    表示される文字列はめちゃくちゃな内容でしょうし、有効なメモリ空間内に文字
    列終端を表すNULL文字が見つからなければ、一般保護違反で異常終了です。

    つまり、例示されたような
    >LPTSTR  getString(){
    > TCHAR value[BUFFER_SIZE];
    > ZeroMemory(value, sizeof(value));
    > GetPrivateProfileString(SECTION_NAME, KEY_NAME, _T(""), value, sizeof(value), FILE_NAME);
    > Return value;
    >}
    をまともに動作させるためには、value変数をスタック領域ではなくヒープ領域に
    確保する必要があります。
    LPTSTR  getString(){
     LPTSTR value = (LPTSTR)malloc(BUFFER_SIZE);
     ZeroMemory(value, sizeof(value));
     GetPrivateProfileString(SECTION_NAME, KEY_NAME, _T(""), value, sizeof(value), FILE_NAME);
     Return value;
    }

    こうすると、getString()関数を抜けても getString()関数の戻り値は有効(= 解
    放されない)です。
    ただし、それと引き替えに、解放するタイミングが分からなくなってしまいます
    ので、自己責任において解放するタイミングを指示せねばなりません。
    それが、free()関数になります。

    以上、ざっと説明でした。

    2013年8月19日 1:16