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

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

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

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

C++

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

Q&A

解決済

3回答

6931閲覧

C++ヒープ領域について

strike1217

総合スコア651

C

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

C++

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

0グッド

0クリップ

投稿2018/07/31 06:23

編集2018/07/31 06:30

C++のヒープ領域について疑問があります。

1つは、reallocの存在についてです。
Bjarne StroustrupによるC++のスタイルとテクニックに関するFAQ

realloc()は、malloc()(とそれと同類の関数)が割り当てた、ユーザ定義の コピーコンストラクタを持たないオブジェクトの配列に対して動作することが保証 されているだけです。

さらに、realloc()は単純な予想に反して、場合によっては 引数の配列をコピーすることがあるということを忘れないでください。
C++でメモリの再割り当てを扱うには、vectorのような標準コンテナを使用する のがよりよい方法です。

ふむ!なるほど・・・
reallocではなく、vectorを使用しなさいと言っています。

vectorが随時伸びるのは良いのですが・・・
newを使った場合に領域を増やしたい!!と言う時はどうするんでしょうか??

**vectorはどうやってヒープ領域を随時伸長させているのか??**ここをヒントに探れば良いんでしょうかね。
というわけで調べてみました。

strace でvectorを見てみました。brkシステムコールが呼ばれています。確かにヒープ領域ですね。(ちなみに、newも同じくbrkシステムコールが呼び出されています。)

munmap(0x7fe0d82b9000, 206565) = 0 brk(NULL) = 0x55a4088ac000 brk(0x55a4088cd000) = 0x55a4088cd000 fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 6), ...}) = 0 write(1, "100\n", 4100 ) = 4 exit_group(0) = ? +++ exited with 0 +++

vectorのpush_back関数はemplace_back関数を呼び出していました。
その中に以下のような関数があります。
おそらくここでヒープ領域を伸ばしているのだ思われます。

#else template<typename _Tp, typename _Alloc> void vector<_Tp, _Alloc>:: _M_realloc_insert(iterator __position, const _Tp& __x) #endif { const size_type __len = _M_check_len(size_type(1), "vector::_M_realloc_insert"); const size_type __elems_before = __position - begin(); pointer __new_start(this->_M_allocate(__len)); pointer __new_finish(__new_start); __try { // The order of the three operations is dictated by the C++11 // case, where the moves could alter a new element belonging // to the existing vector. This is an issue only for callers // taking the element by lvalue ref (see last bullet of C++11 // [res.on.arguments]). _Alloc_traits::construct(this->_M_impl, __new_start + __elems_before, #if __cplusplus >= 201103L std::forward<_Args>(__args)...); #else __x); #endif __new_finish = pointer(); __new_finish = std::__uninitialized_move_if_noexcept_a (this->_M_impl._M_start, __position.base(), __new_start, _M_get_Tp_allocator()); ++__new_finish; __new_finish = std::__uninitialized_move_if_noexcept_a (__position.base(), this->_M_impl._M_finish, __new_finish, _M_get_Tp_allocator()); } __catch(...) { if (!__new_finish) _Alloc_traits::destroy(this->_M_impl, __new_start + __elems_before); else std::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator()); _M_deallocate(__new_start, __len); __throw_exception_again; } std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish, _M_get_Tp_allocator()); _M_deallocate(this->_M_impl._M_start, this->_M_impl._M_end_of_storage - this->_M_impl._M_start); this->_M_impl._M_start = __new_start; this->_M_impl._M_finish = __new_finish; this->_M_impl._M_end_of_storage = __new_start + __len; }

少し長いですね。

また、昔は、vectorはmalloc()が使用されていたようですね。
今は、違うようです。valgrindでメモリリークを調べてみましたが、
All heap blocks were freed -- no leaks are possible
と出てくるので、まぁ心配はないでしょう。
GNU C++ ライブラリの STL コンテナクラスからレポートされるメモリリークについて

んーーーー。イマイチよくわからない・・・。
「vector内部でnewを使用している可能性があるのでは?」と思ったのですが、newとは別に独自に確保関数を作っているんですかね・・・

new式でヒープ領域を随時増やす方法ってどうやるんでしょうか??


2つ目の疑問はnewの使い方についてです。

C++11/14コア言語という書籍の中に以下のような記述を見つけました。

void* operator new(std::size_t size, int , int ,int) throw(std::bad_alloc){ return operator new(size); } void operator delte(void *ptr, int ,int ,int)throw(){ std::cout << "Placement delete" << std::endl; operator delete(ptr); } struct Fail{ Fail() noexcept(false); }; int main(){ Fail *ptr = new(1,2,3)Fail; delete ptr; }

placement newを使用しています。
これっておかしくないでしょうか??
もし、初期化で失敗したら、例外を送出しますよね。
delete(void *ptr, int, int, int)が呼び出されるので、2回の解放関数が呼び出されることになりませんか??
これは脆弱性になりますよね。
しかし、最後のdelete ptrを消すと今度は、メモリリークになる可能性が出てきてしまいます。

うーーーん。
この場合、どうするんでしょうか??

また、例外の実装の箇所に以下のうような記述があります。

std::nothrow_tは、単にオーバーロード解決のためのタグに過ぎない。また、引数として渡しているstd::nothrowは、単に便利な変数である。

namespace std{ struct nothow_t{}; extern const nothow_t nothrow; }

ん??変数の方の定義はどこにあるんです?
これって、定義がなくても使用することってできるんか??

最後の疑問はかなり初歩的です。

struct C{ C(){}; C(int){}; C(int, int){}; } int main(){ new C; new C(0); new C(0, 0); new C{0}; new C{0, 0}; }

最初の3つはコンストラクタを呼んでいるかと思います。
最後の2つです。
これは・・・コンストラクタの方に、std::initializer_listがありませんが・・・
これは、それぞれ、new C(0); new C(0, 0);と同値である。と考えて良いんですか??

分かる方教えてください。環境は、g++ Linuxです。


[追記]
vectorが伸びているのは、再度newを呼び出していました。

pointer │ │99 allocate(size_type __n, const void* = static_cast<const void*>(0)) │ │100 { │ >│101 if (__n > this->max_size()) │ │102 std::__throw_bad_alloc(); │ │103 │ │104 #if __cpp_aligned_new │ │105 if (alignof(_Tp) > __STDCPP_DEFAULT_NEW_ALIGNMENT__) │ │106 { │ │107 std::align_val_t __al = std::align_val_t(alignof(_Tp)); │ │108 return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp), __al)); │ │109 } │ │110 #endif │ │111 return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp))); │ │112 }

おそらく、線形listのように、newを使って新たに領域を作り、そのポインタを連結させているんですかね・・・
つまり・・・newで作成した領域を随時増やす、reallocみたいな関数はnewでは実現不可能・・・ということですかね。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

ふつーに

c++

1// alloc 2char* ptr = new char[size]; 3// realloc 4char* old_ptr = ptr; 5ptr = new char[new_size]; 6std::memcpy(ptr,old_ptr, size); 7delete[] old_ptr;

だったはず

ただ、メモリ割り当てもカスタムしたいという要望に答えるために
カスタムアロケータが用意されたので若干めんどくさいが


もし、初期化で失敗したら、例外を送出しますよね。

えぇ、そうですね。
そして例外ハンドラによって例外が処理され
デフォルトのハンドラであるならばプロセスが終了し
delete ptr;は実行されないでしょう。


これって、定義がなくても使用することってできるんか??

ヘッダに変数定義しちゃったら多重定義で大変なことになります
というわけで、ライブラリのソースにあります。


uniform initialization(統一初期化構文、一様初期化)という、波括弧でコンストラクタ呼び出し(というか初期化)を明示する構文です。
従来の()による初期化は、文脈によって関数定義と見分けがつかないというデメリットがあったので
C++11で追加されました。

というわけで、
new C(0); new C(0, 0)と等価です。

投稿2018/07/31 07:11

asm

総合スコア15147

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/07/31 08:24

「new C(0); new C(0, 0)と等価です。」なるほど! なんか・・・逆にわかりにくいですね。 std::initializer_listがなくても、{}使えるんですね。 realloc関数については、分かりやすい説明ありがとうございます。 「std::memcpy(ptr,old_ptr, size);」この部分なのですが・・・ > ユーザ定義の コピーコンストラクタを持たないオブジェクトの配列に対して動作することが保証 されているだけです。 コピー可能なトリビアルであるクラスの場合はおkということだと思うのですが・・・ コピー可能なトリビアルでなかった場合、std::memcpy()でも大丈夫なんでしょうか??
strike1217

2018/07/31 08:25

extern const nothow_t nothrow; の方はちゃんと定義があるんですね。安心しました。
asm

2018/07/31 09:21

std::unique_ptrを格納しても問題ないし std::move(old_ptr, old_ptr+size, ptr); もしくはそれっぽい何かみたいですね
strike1217

2018/07/31 11:22 編集

なるほど、元々のデータを移すのは、yumetodoさんの回答の方ですね。 「そして例外ハンドラによって例外が処理され デフォルトのハンドラであるならばプロセスが終了」 この場合の「ハンドラ」と言うのは try~catch のcatchの部分ですよね。 明示的に記述されれいない場合は、プロセスを終了するんですね!! あ!そうか! 質問中のコードの場合、初期化で失敗した場合、プロセスを終了してしまうんですね。 2重解放の脆弱性は大丈夫そうですね。
strike1217

2018/07/31 12:03

なるほど!、デフォルトの例外ハンドラだとそうなるんですね。 わかりました。
strike1217

2018/07/31 12:28

BAに迷いましたが、色々回答してくださっているので、選ばさてもらいます!!
guest

0

コピー可能なトリビアルでなかった場合、std::memcpy()でも大丈夫なんでしょうか??

もちろんだめなので、例外安全のために、uninitialized_move_if_noexcept_or_copyのような関数を各実装では内部的に作っています。イメージとしてはこういうやつ

cpp

1 template<typename Iterator> 2 using move_if_noexcept_iterator = std::conditional_t< 3 !std::is_nothrow_move_constructible< 4 typename Iterator::value_type 5 >::value 6 && std::is_copy_constructible< 7 typename Iterator::value_type 8 >::value, 9 Iterator, 10 std::move_iterator<Iterator> 11 >; 12 template<typename Iterator> 13 inline move_if_noexcept_iterator<Iterator> make_move_if_noexcept_iterator(Iterator i) 14 { 15 return move_if_noexcept_iterator<Iterator>(i); 16 } 17 template < 18 typename InputIterator, 19 typename ForwardIterator, 20 enable_if_t< 21 conjunction< 22 is_input_iterator<InputIterator>, 23 is_forward_iterator<ForwardIterator> 24#ifdef _MSC_VER 25 ,negation<std::is_pointer<ForwardIterator>> 26#endif 27 >::value 28 > = nullptr 29 > 30 ForwardIterator uninitialized_move_if_noexcept_or_copy( 31 InputIterator first, 32 InputIterator last, 33 ForwardIterator result 34 ) 35 { 36 return std::uninitialized_copy( 37 inferior::make_move_if_noexcept_iterator(first), 38 inferior::make_move_if_noexcept_iterator(last), 39 result 40 ); 41 }

そのポインタを連結させているんですかね・

ちがいます。

  1. newする(というかアロケーターのallocate関数を呼ぶ
  2. 上記のようなuninitialized_move_if_noexcept_or_copyで例外安全にmoveもしくはcopyする
  3. 古い領域をdelete(というかアロケーターのdeallocate関数を呼ぶ)

そもそもstd::vectorは要素の連続を要求します。ポインタの連結をやっているのはstd::dequeですね


あれですね、一回std::vectorを実装してみますか?実装に必要なヘルパー関数群と実装するメンバ関数の宣言だけのやつに単体テストとbitbucket pipelineの設定付きであとは実装するだけのやつを私のTwitterのフォロワーの人のために作ったのですが、よければ差し上げますよ?

投稿2018/07/31 10:39

編集2018/07/31 11:05
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2018/07/31 11:18

「そもそもstd::vectorは要素の連続を要求します」あ、そうですね。 そこを忘れていました!! 「もちろんだめ」そうですか!!! コピーだとreallocと同じように速度低下を招きそうですね。 超簡単に言うと、「newで新たな領域を作り、元々あったデータをそこに移している」感じなんですね。 これなら、reallocと同ものをC++でも使えますね。 > よければ差し上げますよ? ありがとうございます。是非!
yumetodo

2018/07/31 11:51

>コピーだとreallocと同じように速度低下を招きそうですね。 reallocは忘れてください。あれはcopyでもmoveでもない。 >ありがとうございます。是非! Twitter(@yumetodo)にリプください
strike1217

2018/07/31 12:01

「reallocは忘れてください。あれはcopyでもmoveでもない。」 そうなんですか! コピーかと思ってました。
strike1217

2018/07/31 12:08 編集

twitterのリプって・・・返信のことでしたっけ? 特定のツィートとかに返信すれば良いんですか?
yumetodo

2018/07/31 12:08

単に私にメンションを飛ばせば(@つけて)いいです
Chironian

2018/07/31 12:36

横から失礼。 reallocはメモリをコピーするだけで、クラスをコピーしたりムーブしたりしません。 つまり、(至極当たり前ですが)reallocはコピー・コンストラクタやムーブ・コンストラクタを呼ばないので、それらのコンストラクタの中でメモリ・コピーより高度なことをやっているとreallocでは中途半端な動作になるのでひどい目に合うということなのです。
strike1217

2018/07/31 13:05

> それらのコンストラクタの中でメモリ・コピーより高度なことをやっているとreallocでは中途半端な動作になるのでひどい目に合うということなのです。 fmfm・・・なるほど! C++でreallocの出番は無くなってしまったんですかね。(使う場合は注意が必要ですね) 自作OS用にfree standing環境にSTLを移植することを検討していたので、よくわかりました!! 参考にさせてもらいます。
guest

0

reallocも、新たに領域を確保し、それに乗り換えさせてるだけ、だったりします

これは、それぞれ、new C(0); new C(0, 0);と同値である。と考えて良いんですか??

コンストラクタをオーバーロードさせてるだけ、ですね
引数なしでコンストラクタを呼び出せば C()、
引数一つなら C(int)
引数2つなら C(int,int)
が呼び出されます

投稿2018/07/31 06:38

y_waiwai

総合スコア87719

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問