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

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

ただいまの
回答率

88.78%

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,182

strike1217

score 585

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     #endif111             return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));                                                       │
   │112           }                        


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

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+2

ふつーに

// alloc
char* ptr = new char[size];
// realloc
char* old_ptr = ptr;
ptr = new char[new_size];
std::memcpy(ptr,old_ptr, size);
delete[] old_ptr;

だったはず

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


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

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


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

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


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

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/07/31 20:47

    調べたらC++17でunexpected_handlerが削除されてましたか… ちょっとショックです。

    catchされない例外がある場合
    https://cpprefjp.github.io/reference/exception/terminate.html
    により、異常終了する事が定められているっぽいです。

    キャンセル

  • 2018/07/31 21:03

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

    キャンセル

  • 2018/07/31 21:28

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

    キャンセル

+1

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

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

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

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

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

    template<typename Iterator>
    using move_if_noexcept_iterator = std::conditional_t<
        !std::is_nothrow_move_constructible<
            typename Iterator::value_type
        >::value
        && std::is_copy_constructible<
            typename Iterator::value_type
        >::value,
        Iterator,
        std::move_iterator<Iterator>
    >;
    template<typename Iterator>
    inline move_if_noexcept_iterator<Iterator> make_move_if_noexcept_iterator(Iterator i)
    {
        return move_if_noexcept_iterator<Iterator>(i);
    }
    template <
        typename InputIterator,
        typename ForwardIterator,
        enable_if_t<
            conjunction<
                is_input_iterator<InputIterator>,
                is_forward_iterator<ForwardIterator>
#ifdef _MSC_VER
                ,negation<std::is_pointer<ForwardIterator>>
#endif
            >::value
        > = nullptr
    >
    ForwardIterator uninitialized_move_if_noexcept_or_copy(
        InputIterator first,
        InputIterator last,
        ForwardIterator result
    )
    {
        return std::uninitialized_copy(
            inferior::make_move_if_noexcept_iterator(first),
            inferior::make_move_if_noexcept_iterator(last),
            result
        );
    }

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

ちがいます。

  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 21:08

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

    キャンセル

  • 2018/07/31 21:36

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

    キャンセル

  • 2018/07/31 22:05

    > それらのコンストラクタの中でメモリ・コピーより高度なことをやっているとreallocでは中途半端な動作になるのでひどい目に合うということなのです。

    fmfm・・・なるほど!
    C++でreallocの出番は無くなってしまったんですかね。(使う場合は注意が必要ですね)
    自作OS用にfree standing環境にSTLを移植することを検討していたので、よくわかりました!!
    参考にさせてもらいます。

    キャンセル

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

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

関連した質問

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