簡単な実験関数です。
配列にstaticを付けています。
関数が終了するとwstrが消えてしまうためです。
C
1char *test(int wd){ 2 static char wstr[][10]{ 3 "Sunday", "Monday" .... 4 }; 5 return ((wd >= 0 && wd < 7) ? wstr[wd] : NULL); 6}
しかし、staticにしておくと問題を起こす場合があります。
再帰関数です。
staticが共有されていることを確認してみます。↓
C++
1#include<iostream> 2#include<string> 3#include<memory> 4 5struct node{ 6 std::string name; 7 int no; 8}; 9 10struct node* test_func(){ 11 auto my_del = [](struct node *pInvest){ 12 std::cout << "called deleter!!" << pInvest << std::endl; 13 delete pInvest; 14 }; 15 16 static std::unique_ptr<struct node, decltype(my_del)> ptr(new struct node, my_del); 17 18 std::printf("ptr address = %p\n", ptr.get()); 19 std::cin >> ptr->name; 20 ptr->no = 10; 21 22 return ptr.get(); 23} 24 25int main(){ 26 struct node *str, *wtr; 27 str = test_func(); 28 29 std::cout << str->name << std::endl; 30 std::cout << str->no << std::endl; 31 32 wtr = test_func(); 33 34 std::cout << wtr->name << std::endl; 35 std::cout << wtr->no << std::endl; 36 37 return 0; 38} 39
[結果]
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です。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

回答6件
0
純粋なC言語の場合、言語内で扱える生存期間はauto
(C++の「型推論」という意味ではなく、自動変数という意味です)とstatic
しかなく、それとは別にやろうとすれば、自力でメモリ管理する、という選択肢しかありませんでした。
C++では、クラスのコンストラクタ・デストラクタとともに生存範囲を持てるような、スマートポインタが登場しています。
…というより、後者の例で、スマートポインタをstatic
にしてしまっては、スマートポインタの持つ意味を全力でぶち壊しにしている感じしかありません。スマートポインタからはstatic
を外した上で、struct node *
を返さずに、スマートポインタのインスタンスをそのまま返すようにすれば、問題は解決します。
投稿2018/03/05 08:12
編集2018/03/05 08:16総合スコア146544
0
ベストアンサー
主にCについてですが、C++についても後ほど述べます。(特に明記しない限り、C11、C++17前提です)
staticローカル変数はスコープが狭いグローバル変数である。
staticローカル変数はプログラムの実行時に領域が確保され、プログラムの終了時まで維持されます。ぶっちゃけて言うと、staticローカル変数は、スコープがそのブロック内に限られているだけで、グローバル変数やファイルスコープ変数(トップレベルでstaticがついた変数)と同じと言っても過言ではありません。いずれも、静的記憶期間(static storage duraiton)の変数であり、プログラム内でそれぞれの変数についてただ一つだけ存在し、プログラムの開始から終了まで増えることも減ることもありません。
つまり、staticローカル変数を使う限り、同じ関数を使えば、その関数内のstaticローカル変数も常に同じ存在となります。関数の呼び出し事に異なる変数であることが求められるのであれば、自動変数やスレッドローカル変数を使う以外に方法はありません(スレッドローカル変数はマルチスレッド環境でのみ意味があるのでこの後は言及しません)。
間違いは関数が終了しても確保された領域を使いたいからstaticを使うという所です。確かにstaticを使っても確保された領域が削除されずに関数終了後も参照することができますが、どんな呼び出しの時も常に同じ領域を使うことになることから、関数が二回以上呼ばれる場合を考慮する必要があります。再帰関数に関係なく、この点を考慮しなければ、staticローカル変数の領域を関数外で使うことは思わぬバグになります。
C
1#include <stdio.h> 2#include <string.h> 3char *true_or_false(int x) 4{ 5 static char str[10] = ""; 6 if (x) 7 strcpy(str, "true"); 8 else 9 strcpy(str, "false"); 10 return str; 11} 12int main(void) 13{ 14 char *ans0 = true_or_false(0); 15 printf("%s\n", ans0); 16 char *ans1 = true_or_false(1); 17 printf("%s, %s\n", ans0, ans1); 18 return 0; 19}
ans0
について何もしていないのに、二回目のtrue_or_false()
の呼び出しの後はその中身は変わってしまっています。
別の物として扱いたいなら動的に確保する必要がある。
これを防ぐには関数が呼び出されるときに動的に領域を確保するしかありません。
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4char *true_or_false(int x) 5{ 6 char *str = (char *)malloc(10); 7 if (str == NULL) 8 abort(); 9 if (x) 10 strcpy(str, "true"); 11 else 12 strcpy(str, "false"); 13 return str; 14} 15int main(void) 16{ 17 char *ans0 = true_or_false(0); 18 printf("%s\n", ans0); 19 char *ans1 = true_or_false(1); 20 printf("%s, %s\n", ans0, ans1); 21 free(ans0); 22 free(ans1); 23 return 0; 24}
今度はans0
は二回目呼び出しで影響を受けません。一回目の二回目で確保される領域が異なるからです。ただ、malloc()
等で動的に領域を確保した場合、確保失敗時の処理の追加やfree()
での解放が必要になります。
この方法を採用しているライブラリではfree()
ではなくを専用の関数を用意している場合があります。たとえば、libcurlでは、curl_easy_escape()
等で動的に確保された文字列はcurl_free()
で解放する事になっています。
領域は呼び出し側で用意する。
慣れない内はメモリ管理は面倒です。メモリリークも注意しなくてはなりません。では、それ以外の方法はないのかというと、発想を変えて、関数側が領域を用意するのではなく、呼び出し側が領域を用意するというのがあります。
C
1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4char *true_or_false(int x, char *str, size_t n) 5{ 6 if (x) 7 strncpy(str, "true", n); 8 else 9 strncpy(str, "false", n); 10 str[n - 1] = '\0'; // 必ずnull終端になるようにする。 11 return str; 12} 13int main(void) 14{ 15 char ans0[10]; 16 true_or_false(0, ans0, 10); 17 printf("%s\n", ans0); 18 char ans1[10]; 19 true_or_false(1, ans1, 10); 20 printf("%s, %s\n", ans0, ans1); 21 return 0; 22}
こちらの場合は呼び出し側が領域を完全に管理しているので、呼び出し側で自動変数をつかうという手段が使えます。ただ、大きさなどの情報も一緒に渡してバッファオーバーランしないように注意が必要です。
上の実装では確保された領域が小さかったら途中までしか入らないとしていますが、ライブラリ等では必要な大きさを返す仕組みを持つ物もあります。返り値にしたり、サイズ部分をポインタにしたり等で実装が可能です。Win32 APIの多くの関数がこの方法を採用しています。
[C++] スマートポインタで返す。
malloc()
等を用いた問題はメモリ管理の猥雑さでした。それを少しだけ緩和してくれるのがスマートポインタです(GCのような完全な管理を期待してはいけません)。
C++
1#include <cstdio> 2#include <cstring> 3#include <memory> 4std::unique_ptr<char[]> true_or_false(int x) 5{ 6 std::unique_ptr<char[]> str = std::make_unique<char[]>(10); 7 if (x) 8 strcpy(str.get(), "true"); 9 else 10 strcpy(str.get(), "false"); 11 return str; 12} 13int main(void) 14{ 15 auto ans0 = true_or_false(0); 16 printf("%s\n", ans0.get()); 17 auto ans1 = true_or_false(1); 18 printf("%s, %s\n", ans0.get(), ans1.get()); 19 return 0; 20}
違いがわかりやすいようにレガシーなCの関数はそのままでスマートポインタを使ってみました。この場合はfree()
は必要なく、ローカル変数ans0
やans1
の寿命が消えると自動的にstd::make_unique()
で確保された領域も削除されます。
[C++] 右辺値参照としてムーブする。
最後に右辺値参照を使う方法です。
C++
1#include <iostream> 2#include <string> 3#include <utility> 4std::string true_or_false(int x) 5{ 6 std::string str; 7 if (x) 8 str = "true"; 9 else 10 str = "false"; 11 return std::move(str); 12} 13int main(void) 14{ 15 auto ans0 = true_or_false(0); 16 std::cout << ans0 << std::endl; 17 auto ans1 = true_or_false(1); 18 std::cout << ans0 << ", " << ans1 << std::endl; 19 return 0; 20}
右辺値参照を使わずに値で返すという方法もあります。しかし、この場合は大きなメモリコピーが発生するため、場合によってはパフォーマンスに重大な影響を与えます。そこで、std::move()
を用いて右辺値参照として返すようにします。例え文字列が大きくなっても、関数から戻ってきて代入(実際はムーブ)されるところの速度は変わりません。
上のコードですが、std::move()
を外した方が効率がいいです。自動ローカル変数をそのまま返す場合、その変数が戻り値の領域に初めからセットされるからです。戻り値は右辺値ですので、呼び出し側が対応していれば、右辺値参照によるムーブセマンティクスによって、値のムーブになります。(詳しくはChironianさんのコメント参照)
C/C++で実装することをやめる。
暴論を言うと、GCがある言語を使えば、そんな悩みはしなくて済みます。どうしてもC/C++でないと嫌だというのであれば、Boehm GC等を使うという方法も一応あります(Crystalもこのライブラリ使っているし、そこそこいけているんじゃないのかなー、使ったことないけど)。
ということで、まずは安易にstaticローカル変数を使うという選択肢を外して下さい。staticローカル変数は結構扱いが難しい(というより、使う場面が限られている)ものです。面倒でもCであれば動的領域確保や呼び出しが領域を用意するという手段を取って下さい。C++であれば、スマートポインタやムーブセマンティクスを活用すると良いでしょう。
投稿2018/03/05 14:25
編集2018/03/05 22:06総合スコア21749
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/05 14:41
2018/03/05 18:16 編集
2018/03/06 02:05

0
こんにちは。
関数から戻っても有効な値を返却したいなら、普通にオブジェクトをコピーして返却すればよいですよ。
呼び出す度に同じメモリを返却するのなら元の関数で良いですし、呼び出す度に異なるメモリを返却するならコピーが必要ですから。
C++
1#include <iostream> 2#include <string> 3 4std::string test(int wd) 5{ 6 static char const* wstr[]{ 7 "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" 8 }; 9 return ((wd >= 0 && wd < 7) ? wstr[wd] : NULL); 10} 11 12int main() 13{ 14 auto weekday0 = test(0); 15 std::cout << "weekday0=" << weekday0 << " : " << &weekday0 << "\n"; 16 auto weekday1 = test(1); 17 std::cout << "weekday1=" << weekday1 << " : " << &weekday1 << "\n"; 18}
実行結果:
weekday0=Sunday : 0x7ffdc1db7e80 weekday1=Monday : 0x7ffdc1db7e60
投稿2018/03/05 08:54
総合スコア23274
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
staticを付与しない場合は、関数終了時に変数が利用できなくなります。次の関数呼び出しまで値を保存し続けることはありません(ローカル変数の性質上当たり前)。
staticを付与した場合は、関数終了後も値を保存します。ただし、再帰関数等の場合、値を共有するので、値を書き換えるとほかにも影響が出る(これも当たり前)
結局、どのようなことがしたいのでしょうか?
共有する変数を作りたいのか、共有しない変数をつくりたいのか、そもそも目的が見えません。ちなみにグローバル変数を使うとか呼び出し元の変数で状態管理する(引数で状態を受け取る)といった運用も考えられます。
投稿2018/03/05 12:23
総合スコア4853
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
文字列の方
曜日の名前を取得するだけならconst char*貰えばいいですね。
書き換えたいのなら貰った側がコピーすればいいのです。
c++
1const char *test(int wd){ 2 static const char wstr[][10]{ 3 "Sunday", "Monday" .... 4 }; 5 return ((wd >= 0 && wd < 7) ? wstr[wd] : NULL); 6}
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++だと思います。
投稿2018/03/05 14:59
編集2018/03/05 15:02総合スコア15149
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
普通にAUTO変数にして初期化しましょう
って、関数が終わっても破棄されたら困るのか。
なら、呼び出されるたびに malloc で領域確保して初期化するってことぐらいだけど、
そうなければならないってところに設計が悪い気がしますねー
#freeするタイミングはどーする?
投稿2018/03/05 08:04
編集2018/03/05 08:08総合スコア88163
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/03/05 08:17 編集
2018/03/05 08:19
2018/03/05 08:21
2018/03/05 08:25
2018/03/05 08:26
2018/03/05 08:29
2018/03/05 08:29
2018/03/05 08:33
2018/03/05 08:34 編集
2018/03/05 08:35
2018/03/05 08:35
2018/03/05 08:37
2018/03/05 08:37
2018/03/05 08:41
2018/03/05 09:26
2018/03/05 09:44
2018/03/05 10:02
2018/03/05 10:04
2018/03/05 10:07
2018/03/05 10:08
2018/03/05 10:12 編集
2018/03/05 10:13
2018/03/05 10:22
2018/03/05 10:59 編集
2018/03/05 11:01
2018/03/05 11:09
2018/03/05 11:17