質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.40%

  • C

    4806questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • C++

    4647questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

staticの非共有化

解決済

回答 6

投稿

  • 評価
  • クリップ 0
  • VIEW 944

strike1217

score 566

簡単な実験関数です。

配列にstaticを付けています。
関数が終了するとwstrが消えてしまうためです。

char *test(int wd){
     static char wstr[][10]{
          "Sunday", "Monday" ....
     };
     return ((wd >= 0 && wd < 7) ? wstr[wd] : NULL);
}


しかし、staticにしておくと問題を起こす場合があります。
再帰関数です。

staticが共有されていることを確認してみます。↓

#include<iostream>
#include<string>
#include<memory>

struct node{
    std::string name;
    int no;
};

struct node* test_func(){
    auto my_del = [](struct node *pInvest){
        std::cout << "called deleter!!" << pInvest << std::endl;
        delete pInvest;
    };

    static std::unique_ptr<struct node, decltype(my_del)> ptr(new struct node, my_del);

    std::printf("ptr address = %p\n", ptr.get());
    std::cin >> ptr->name;
    ptr->no = 10;

    return ptr.get();
}

int main(){
    struct node *str, *wtr;
    str = test_func();

    std::cout << str->name << std::endl;
    std::cout << str->no << std::endl;

    wtr = test_func();

    std::cout << wtr->name << std::endl;
    std::cout << wtr->no << std::endl;

    return 0;
}


[結果]
ptr address = 0x5585759fce70
jreoeau
jreoeau
10
ptr address = 0x5585759fce70
reiwu
reiwu
10
called deleter!!0x5585759fce70

同じアドレスが表示されるため、staticが共有されていることが確認できます。

しかし、
再帰関数にするとこの共有された変数が呼び出された関数によって崩されてしまいます。

そこで、staticを関数毎に確保する方法ってあるんでしょうか??
関数内のstaticの非共有化できれば良いのですが・・・

[自分が考えた方法]
staticを配列にしておいて、関数が呼び出されるときに参照カウンタをインクリメントする。その関数内では配列のカウンタが指す要素を利用する。
関数が終了する時に、カウンタをデクリメントする。

・・・・面倒です。!

関数内のstaticの非共有化は調べてみたのですが、でてきません。

分かる方いますか??
教えてください。

gcc 7.3.0 Linuxです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 6

+4

純粋なC言語の場合、言語内で扱える生存期間はauto(C++の「型推論」という意味ではなく、自動変数という意味です)とstaticしかなく、それとは別にやろうとすれば、自力でメモリ管理する、という選択肢しかありませんでした。

C++では、クラスのコンストラクタ・デストラクタとともに生存範囲を持てるような、スマートポインタが登場しています。

…というより、後者の例で、スマートポインタをstaticにしてしまっては、スマートポインタの持つ意味を全力でぶち壊しにしている感じしかありません。スマートポインタからはstaticを外した上で、struct node *を返さずに、スマートポインタのインスタンスをそのまま返すようにすれば、問題は解決します。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/05 20:01

    ああ!
    なるほど! 長期間ほったらかしていてもメモリリークと見なせるんですね。

    キャンセル

  • 2018/03/05 20:09

    一般的なC言語環境であれば、staticに必要なメモリはコンパイル時に決まるので、プログラムを起動してから終了するまで変化することはありません(だからこそ「静的」記憶域です)。

    関数の実行ごとに確保するメモリは、分量も決まらないので、当然コンパイル時に静的に確保する訳にはいきません。

    キャンセル

  • 2018/03/05 20:17

    その通りですね。

    キャンセル

checkベストアンサー

+3

主にCについてですが、C++についても後ほど述べます。(特に明記しない限り、C11、C++17前提です)

 staticローカル変数はスコープが狭いグローバル変数である。

staticローカル変数はプログラムの実行時に領域が確保され、プログラムの終了時まで維持されます。ぶっちゃけて言うと、staticローカル変数は、スコープがそのブロック内に限られているだけで、グローバル変数やファイルスコープ変数(トップレベルでstaticがついた変数)と同じと言っても過言ではありません。いずれも、静的記憶期間(static storage duraiton)の変数であり、プログラム内でそれぞれの変数についてただ一つだけ存在し、プログラムの開始から終了まで増えることも減ることもありません。

つまり、staticローカル変数を使う限り、同じ関数を使えば、その関数内のstaticローカル変数も常に同じ存在となります。関数の呼び出し事に異なる変数であることが求められるのであれば、自動変数やスレッドローカル変数を使う以外に方法はありません(スレッドローカル変数はマルチスレッド環境でのみ意味があるのでこの後は言及しません)。

間違いは関数が終了しても確保された領域を使いたいからstaticを使うという所です。確かにstaticを使っても確保された領域が削除されずに関数終了後も参照することができますが、どんな呼び出しの時も常に同じ領域を使うことになることから、関数が二回以上呼ばれる場合を考慮する必要があります。再帰関数に関係なく、この点を考慮しなければ、staticローカル変数の領域を関数外で使うことは思わぬバグになります。

#include <stdio.h>
#include <string.h>
char *true_or_false(int x)
{
    static char str[10] = "";
    if (x)
        strcpy(str, "true");
    else
        strcpy(str, "false");
    return str;
}
int main(void)
{
    char *ans0 = true_or_false(0);
    printf("%s\n", ans0);
    char *ans1 = true_or_false(1);
    printf("%s, %s\n", ans0, ans1);
    return 0;
}

ans0について何もしていないのに、二回目のtrue_or_false()の呼び出しの後はその中身は変わってしまっています。

 別の物として扱いたいなら動的に確保する必要がある。

これを防ぐには関数が呼び出されるときに動的に領域を確保するしかありません。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *true_or_false(int x)
{
    char *str = (char *)malloc(10);
    if (str == NULL)
        abort();
    if (x)
        strcpy(str, "true");
    else
        strcpy(str, "false");
    return str;
}
int main(void)
{
    char *ans0 = true_or_false(0);
    printf("%s\n", ans0);
    char *ans1 = true_or_false(1);
    printf("%s, %s\n", ans0, ans1);
    free(ans0);
    free(ans1);
    return 0;
}

今度はans0は二回目呼び出しで影響を受けません。一回目の二回目で確保される領域が異なるからです。ただ、malloc()等で動的に領域を確保した場合、確保失敗時の処理の追加やfree()での解放が必要になります。

この方法を採用しているライブラリではfree()ではなくを専用の関数を用意している場合があります。たとえば、libcurlでは、curl_easy_escape()等で動的に確保された文字列はcurl_free()で解放する事になっています。

 領域は呼び出し側で用意する。

慣れない内はメモリ管理は面倒です。メモリリークも注意しなくてはなりません。では、それ以外の方法はないのかというと、発想を変えて、関数側が領域を用意するのではなく、呼び出し側が領域を用意するというのがあります。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *true_or_false(int x, char *str, size_t n)
{
    if (x)
        strncpy(str, "true", n);
    else
        strncpy(str, "false", n);
    str[n - 1] = '\0'; // 必ずnull終端になるようにする。
    return str;
}
int main(void)
{
    char ans0[10];
    true_or_false(0, ans0, 10);
    printf("%s\n", ans0);
    char ans1[10];
    true_or_false(1, ans1, 10);
    printf("%s, %s\n", ans0, ans1);
    return 0;
}

こちらの場合は呼び出し側が領域を完全に管理しているので、呼び出し側で自動変数をつかうという手段が使えます。ただ、大きさなどの情報も一緒に渡してバッファオーバーランしないように注意が必要です。

上の実装では確保された領域が小さかったら途中までしか入らないとしていますが、ライブラリ等では必要な大きさを返す仕組みを持つ物もあります。返り値にしたり、サイズ部分をポインタにしたり等で実装が可能です。Win32 APIの多くの関数がこの方法を採用しています。

 [C++] スマートポインタで返す。

malloc()等を用いた問題はメモリ管理の猥雑さでした。それを少しだけ緩和してくれるのがスマートポインタです(GCのような完全な管理を期待してはいけません)。

#include <cstdio>
#include <cstring>
#include <memory>
std::unique_ptr<char[]> true_or_false(int x)
{
    std::unique_ptr<char[]> str = std::make_unique<char[]>(10);
    if (x)
        strcpy(str.get(), "true");
    else
        strcpy(str.get(), "false");
    return str;
}
int main(void)
{
    auto ans0 = true_or_false(0);
    printf("%s\n", ans0.get());
    auto ans1 = true_or_false(1);
    printf("%s, %s\n", ans0.get(), ans1.get());
    return 0;
}

違いがわかりやすいようにレガシーなCの関数はそのままでスマートポインタを使ってみました。この場合はfree()は必要なく、ローカル変数ans0ans1の寿命が消えると自動的にstd::make_unique()で確保された領域も削除されます。

 [C++] 右辺値参照としてムーブする。

最後に右辺値参照を使う方法です。

#include <iostream>
#include <string>
#include <utility>
std::string true_or_false(int x)
{
    std::string str;
    if (x)
        str = "true";
    else
        str = "false";
    return std::move(str);
}
int main(void)
{
    auto ans0 = true_or_false(0);
    std::cout << ans0 << std::endl;
    auto ans1 = true_or_false(1);
    std::cout << ans0 << ", " << ans1 << std::endl;
    return 0;
}

右辺値参照を使わずに値で返すという方法もあります。しかし、この場合は大きなメモリコピーが発生するため、場合によってはパフォーマンスに重大な影響を与えます。そこで、std::move()を用いて右辺値参照として返すようにします。例え文字列が大きくなっても、関数から戻ってきて代入(実際はムーブ)されるところの速度は変わりません。

上のコードですが、std::move()を外した方が効率がいいです。自動ローカル変数をそのまま返す場合、その変数が戻り値の領域に初めからセットされるからです。戻り値は右辺値ですので、呼び出し側が対応していれば、右辺値参照によるムーブセマンティクスによって、値のムーブになります。(詳しくはChironianさんのコメント参照)

 C/C++で実装することをやめる。

暴論を言うと、GCがある言語を使えば、そんな悩みはしなくて済みます。どうしてもC/C++でないと嫌だというのであれば、Boehm GC等を使うという方法も一応あります(Crystalもこのライブラリ使っているし、そこそこいけているんじゃないのかなー、使ったことないけど)。


ということで、まずは安易にstaticローカル変数を使うという選択肢を外して下さい。staticローカル変数は結構扱いが難しい(というより、使う場面が限られている)ものです。面倒でもCであれば動的領域確保や呼び出しが領域を用意するという手段を取って下さい。C++であれば、スマートポインタやムーブセマンティクスを活用すると良いでしょう。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/06 07:57

    ん?RVOって最適化しなくても発動するんですかね?

    普通にコンパイルしても「コピーの省略」が行われるんですか?

    キャンセル

  • 2018/03/06 11:05

    raccyさん。

    > そっちの先で単純な代入とかだったら、ムーブになったりするって事なのかー。

    ですね。この辺、ややこしいですよね。
    代入構文(node aNode; aNode=test_func();)なら、ムーブ代入演算子があればムーブになります。
    初期化構文(node aNode=test_func();)の時は、aNode上に直接コンストラクトされるようです。(gcc, clangの場合。msvcは未確認)

    strike1217さん。

    > 普通にコンパイルしても「コピーの省略」が行われるんですか?

    少なくともC++17より前の規格の場合は、コピーの省略をするかしないかは処理系依存です。

    因みに、wandboxで確認した時はgcc, clang共に最適化無しでもコピーは省略されていました。
    ↓でそこそこ詳しく解説してますので、よかったら参考にしてみて下さい。
    https://theolizer.com/cpp-school1/cpp-school1-37/

    キャンセル

  • 2018/03/06 13:45

    なるほど!!
    わかりました。

    キャンセル

+2

こんにちは。

関数から戻っても有効な値を返却したいなら、普通にオブジェクトをコピーして返却すればよいですよ。
呼び出す度に同じメモリを返却するのなら元の関数で良いですし、呼び出す度に異なるメモリを返却するならコピーが必要ですから。

#include <iostream>
#include <string>

std::string test(int wd)
{
     static char const* wstr[]{
          "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
     };
     return ((wd >= 0 && wd < 7) ? wstr[wd] : NULL);
}

int main()
{
    auto weekday0 = test(0);
    std::cout << "weekday0=" << weekday0 << " : " << &weekday0 << "\n";
    auto weekday1 = test(1);
    std::cout << "weekday1=" << weekday1 << " : " << &weekday1 << "\n";
}

実行結果:

weekday0=Sunday : 0x7ffdc1db7e80
weekday1=Monday : 0x7ffdc1db7e60


wandbox

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/05 22:59

    > ところで、staticの非共有化自体の方はC++で可能なんですか?

    必要なstaticローカル変数の数に上限があるなら、配列で確保して先頭から順に使う手はあります。
    https://wandbox.org/permlink/QYtgTF7KASfzLNTZ

    しかし、メモリの許す限り使いたい場合は不可能です。静的なメモリ(static変数含む)はコンパイル時にサイズが決まっている必要がありますから。
    メモリの許す限り使いたい時は動的なメモリ(通常はヒープ)を使います。

    キャンセル

  • 2018/03/05 23:05 編集

    > std::stackを使って再帰関数呼び出し前と後でpush pop してstatic std::uniqueを復活させるという方法も思いつきました。

    これはダメでは?
    popしたstd::unique_ptrがスコープから外れた時点でポインタの先のメモリが開放されますから。(次にpopしたものを上書きした時です。)

    キャンセル

  • 2018/03/05 23:25

    なるほど!
    わかりました。

    C/C++ではstaticの非共有化は不可能で、自分で作るしかないわけですね。
    ケイロニアンさんの方法を採用します。

    キャンセル

+1

staticを付与しない場合は、関数終了時に変数が利用できなくなります。次の関数呼び出しまで値を保存し続けることはありません(ローカル変数の性質上当たり前)。

staticを付与した場合は、関数終了後も値を保存します。ただし、再帰関数等の場合、値を共有するので、値を書き換えるとほかにも影響が出る(これも当たり前)

 結局、どのようなことがしたいのでしょうか?

共有する変数を作りたいのか、共有しない変数をつくりたいのか、そもそも目的が見えません。ちなみにグローバル変数を使うとか呼び出し元の変数で状態管理する(引数で状態を受け取る)といった運用も考えられます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/05 21:30

    超簡単に言いますと、
    静的記憶期間に配置しつつ、値を共有しない変数の作成ですね。


    仰る通りで、
    staticなしー>関数終了時に変数は利用できなくなる。共有はされない。
    staticありー>関数終了時に消えないが、共有される。

    つまり、関数終了時に消えないで、共有されないような変数です。
    staticの非共有化です。

    そのようなことは可能でしょうか??という質問です。

    キャンセル

  • 2018/03/05 21:37 編集

    C言語では不可能であることはmaisumakunさんが言っているのですが、C++にそのような機能がありそうな感じはしますが・・・

    キャンセル

0

普通にAUTO変数にして初期化しましょう

って、関数が終わっても破棄されたら困るのか。
なら、呼び出されるたびに malloc で領域確保して初期化するってことぐらいだけど、
そうなければならないってところに設計が悪い気がしますねー
#freeするタイミングはどーする?

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/05 17:17

    なら、回答にあるようにmallocでヒープ確保していく手だけど、これ、完全に設計が悪いですぜ。
    考え直したほうがよろしいかと。

    キャンセル

  • 2018/03/05 17:22

    ああ!
    wstrのような配列をmallocにすれば、staticじゃなくてもreturnできるよ!
    って言いたいんですね。

    んー。それもそうですね。

    キャンセル

  • 2018/03/05 17:28

    まあ、どのみち関数を再帰するばあいは無限に再起しないように注意しましょう、ってことでw

    キャンセル

0

文字列の方
曜日の名前を取得するだけならconst char*貰えばいいですね。
書き換えたいのなら貰った側がコピーすればいいのです。

const char *test(int wd){
     static const char wstr[][10]{
          "Sunday", "Monday" ....
     };
     return ((wd >= 0 && wd < 7) ? wstr[wd] : NULL);
}

nodeの方
ポインタいりますか?

#include<iostream>
#include<string>
#include <utility>

struct node{
    std::string name;
    int no;
};

struct node test_func(){
    std::string name;
    std::cin >> name;
    int no = 10;

    return node{std::move(name), no};
}

int main(){
    node str = test_func();
    node wtr = test_func();

    std::cout << str.name << std::endl;
    std::cout << str.no << std::endl;
    std::cout << wtr.name << std::endl;
    std::cout << wtr.no << std::endl;
    return 0;
}

ポインタをできるだけ排斥してSTLやスマートポインタ、参照を使うのがモダンなC++だと思います。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.40%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • C

    4806questions

    C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

  • C++

    4647questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。