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

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

新規登録して質問してみよう
ただいま回答率
85.68%
Visual C++

Microsoft Visual C++はWindowsのCとC++の統合開発環境(IDE)であり、コンパイラやデバッガを含んでいます。

C++

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

Q&A

解決済

1回答

914閲覧

グローバル空間のポインタにおけるメモリの解放の必要性の真偽について(c++)

退会済みユーザー

退会済みユーザー

総合スコア0

Visual C++

Microsoft Visual C++はWindowsのCとC++の統合開発環境(IDE)であり、コンパイラやデバッガを含んでいます。

C++

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

0グッド

1クリップ

投稿2023/04/03 04:43

概要

グローバル空間に定義されたポインタ変数に、関数中で動的に確保したメモリへのポインタを保持するようなプログラムで、そのポインタが指すメモリを解放する必要があるかどうか知りたいです。

前提

確認として、私はそもそも動的に確保したメモリはプログラムが終わるまでに解放する必要があると教わりました。

しかし、先日グローバル空間に定義されたポインタに確保したメモリを割り当てて、そのまま解放しないプログラムと出会い、書いた人に確認したところ、そのままで大丈夫だと言われましたが結局腑に落ちないままになっています。

元のコードは載せることができないのですが、MSVCのstd::chrono::get_tzdb_listの実装を見ていてぱっと見同じようなことが行われているような気がしました。

それを参考に以下のようなプログラムを作成しました。

ソースコード

c++

1#include <iostream> 2#include <atomic> 3#include <chrono> 4 5struct A 6{ 7 A() { std::cout << "Created" << std::endl; } 8 ~A() { std::cout << "Destroyed" << std::endl; } 9}; 10 11inline std::atomic<A*> g_a; 12 13inline A& get_a() 14{ 15 auto a_ptr = g_a.load(); 16 if (a_ptr != nullptr) 17 return *a_ptr; 18 19 auto my_a = static_cast<A*>(std::calloc(1, sizeof(A))); 20 if (my_a == nullptr) 21 throw std::runtime_error("bad alloc"); 22 23 try 24 { 25 std::construct_at(my_a); 26 } 27 catch (const std::runtime_error&) 28 { 29 std::free(my_a); 30 throw; 31 } 32 catch (const std::exception& e) 33 { 34 std::free(my_a); 35 throw std::runtime_error(e.what()); 36 } 37 38 if (g_a.compare_exchange_strong(a_ptr, my_a)) 39 { 40 a_ptr = my_a; 41 } 42 else 43 { 44 std::destroy_at(my_a); 45 std::free(my_a); 46 } 47 48 return *my_a; 49} 50 51int main() 52{ 53 try 54 { 55 auto& tzdbl = get_a(); 56 } 57 catch (std::exception& e) 58 { 59 std::cerr << e.what() << '\n'; 60 } 61 62 return 0; 63}

出力

Created D:\Lab\Repos\Test\x64\Debug\Test.exe (プロセス 39856) は、コード 0 で終了しました。 このウィンドウを閉じるには、任意のキーを押してください...

知りたいこと

出力からもわかる通りメモリは解放されていない?(デストラクタが呼ばれていない)ことがわかりました。そのため、結局なぜメモリを開放しなくてもいいのかわかりませんでした。 

以上を踏まえて、

  • なぜ解放しないで大丈夫と言われたのか
  • そもそもソースコードはおかしくないか
  • 根本から勘違いしてることはないか

の3点についてご教示いただけると幸いです。

補足情報(FW/ツールのバージョンなど)

実行環境: Windows10 Visual Studio 2022 ver17.5.3(/std:c++20)

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

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

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

Zuishin

2023/04/03 05:14

プログラムが終了するまでずっとそのままなら解放する必要はありません。 なぜならプロセス終了時に自動的に解放されるからです。
退会済みユーザー

退会済みユーザー

2023/04/03 05:45

それってグローバルだからとかって関係ありますか? それとも、実行時に確保したメモリはすべてプログラムの終了時に解放されるということであってますか? なんとなくで理解できてないです。 あとその場合デストラクタって呼ばれますか?
Zuishin

2023/04/03 05:54 編集

すべて解放されますが、関数内で確保してローカル変数で使い、開放せず放置したものはメモリリークの現因にならない保証がないので、可読性が犠牲になります。 プログラムの実行時間と寿命が等しいようなメモリ以外は、スマートポインタを使って必ず全て解放する決まりにしておくのが良いでしょう。
退会済みユーザー

退会済みユーザー

2023/04/03 05:54

なるほど、メモリリークについて誤解していました。理解しました。
otn

2023/04/03 06:49

普通に確保するメモリは、そのプロセスの仮想メモリ空間内での話なので、プロセスが終了すると仮想メモリ空間ごと消えます。 ただし、プロセス間でメモリを共有することが必要なケースで、OSに共有メモリの確保を依頼するAPIを使った場合は、プロセスが終了してもOSは自動的に解放しないのが普通なので、適切に解放する必要があります。ただ、この機能を普通の人が使うことは無いと思います。
dameo

2023/04/03 13:51 編集

そもそもWindowsの話だと32bit以降ファイル マッピングなので自動で開放されちゃったりします。一般的なOSの話だとそういうの(shm_open)もありますね。GlobalAllocで共有出来てた16bit時代もリークはしなかったような・・・。 コードについてですが、C++20のようなので、そろそろタグにC++20が欲しいですね。あと一応std::bad_allocって例外もありますよ。std::exception派生ですが。 プロセス終了時のオブジェクト破棄時間が長くなりすぎるときはメモリだけなら故意にリークもあるけど、C++だとallocatorもあるので、そこまで気にするならallocatorで一気に開放する仕組みを用意してもいいかも。 さらに追加で言葉の定義の問題ですが、例えば #include <iostream> struct singleton { static singleton& instance() { if (! instance_) { instance_ = new singleton(); } return *instance_; } void hello() {std::cout << "hello" << std::endl;} private: static inline singleton* instance_ = nullptr; singleton() {std::cout << "constructed" << std::endl;} ~singleton() {std::cout << "destructed" << std::endl;} }; int main() { singleton::instance().hello(); return 0; } このプログラム、g++に-fsanitize=leak指定してリーク検出させようとしても報告されません。元来(狭義の)メモリリークは未管理状態になってしまって開放することができないメモリ(ポインタ)のことなので、ちゃんとsingleton::instance_に残ってるなら未管理=リークということにならないわけです。 グローバルなら未管理にはならないので、多分質問意図としてはこちらの話な気がします。シングルトンはインスタンスが1つであることを保証する仕組みですが、中身はただのグローバル変数ですよね。
退会済みユーザー

退会済みユーザー

2023/04/03 22:54

フォローアップ有難うございます。
guest

回答1

3

ベストアンサー

MSVCのstd::chrono::get_tzdb_listの実装を見ていて

この関数の返り値はシングルトンオブジェクトである、つまり「プログラム終了まで開放されないことが保証されている」値なのです(cpprefjp)。

一般的な、使ったあとどこかで有効期限を迎えるような値とは全く性質が異なります。

投稿2023/04/03 06:11

maisumakun

総合スコア144195

Zuishin, macof👍を押しています

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

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

このような回答には修正を依頼しましょう。

また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。

回答へのコメント

退会済みユーザー

退会済みユーザー

2023/04/03 06:32

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.68%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

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

Visual C++

Microsoft Visual C++はWindowsのCとC++の統合開発環境(IDE)であり、コンパイラやデバッガを含んでいます。

C++

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