C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

C++ サイズ0でnewした場合

[トピック内 7 記事 (1 - 7 表示)]  << 0 >>

■95391 / inTopicNo.1)  C++ サイズ0でnewした場合
  
□投稿者/ az (11回)-(2020/07/28(Tue) 14:06:59)

分類:[.NET 全般] 

Visual Studio 2010 Professionalを使い、C++で開発しています。

コーディングミスにより、サイズ0でnewする処理が存在するのを見つけました。
この処理は1秒おきに呼び出され、24時間以上動作し続けるソフトウェアに含まれています。

今回、サイズ0でnewする処理が1秒おきに実行され続けていましたが、たまたまエラーや異常終了することなく動いていました。
しかし、メモリ使用量(Private Bytes)は上昇し続けていました。

挙動について、教えてください。

(1)サイズ0でnewした場合、以下のサイトを参考にする限りでは
「size が 0 でも他の確保済みの記憶域と異なるアドレスを返す。」ようですが
何らかのメモリ空間の確保がされているのでしょうか。
https://cpprefjp.github.io/reference/new/op_new.html


(2)アドレスを返すということは、delete処理は必須なのではないでしょうか。


引用返信 編集キー/
■95394 / inTopicNo.2)  Re[1]: C++ サイズ0でnewした場合
□投稿者/ 774RR (813回)-(2020/07/28(Tue) 15:30:01)
サイズ0ってのは int* p=new int[0]; の意味? (new int で 0 バイトを取ることはできないので)

JIS X 3014:2003 5.3.4 new 式の 7
直接 new 宣言子の中の式の値がゼロの場合、割付関数が呼び出され、要素のない配列が割り付けられる。
と規定されているため、規格合致処理系であれば
・要素数0のメモリが確保される(管理用オーバーヘッドを含めると0バイトより大きい)
・プログラマは返された領域を使ってはならない
・ delete[] するまでメモリは使用済みであって再利用されない
ってことっス。
# 同 6 には非負とあるので、負数を渡すのは違反

この辺は C の malloc(0) の挙動にも似ていて JIS X 3010:2003 7.20.3
・0バイトのオブジェクトを割り当ててその先頭へのポインタを返した場合には
・プログラマは返された領域を使ってはならない
・返された領域に対して realloc を使ってよい
・返された領域に対して free を使ってよい( free するまで再利用されない)
・返された領域は管理領域を含めると0バイトより大きい

ってことで delete[] しないとメモリリークするですよ。

引用返信 編集キー/
■95398 / inTopicNo.3)  Re[2]: C++ サイズ0でnewした場合
□投稿者/ az (12回)-(2020/07/28(Tue) 16:19:20)
No95394 (774RR さん) に返信


回答ありがとうございます。
先ほど、JIS X 3014:2003 5.3.4 new 式の 7と8を読みました。


> サイズ0ってのは int* p=new int[0]; の意味? (new int で 0 バイトを取ることはできないので)

⇒ そうです。
   厳密には、以下のようなstructを定義してあり
   
    typedef struct
    {
        char* pcA;
        char* pcB;
    }Hoge;

    
    イベント内で
     Hogeの配列をnewで作成しています。
    しかし、sizeの部分(int型)は、都度計算しており 0になることもあり得ます。


       Hoge*  pHoge = NULL;

       pHoge = new Hoge[size];


  sizeをチェックするようにし、
    size>0の時に限り new してHogeの配列を作成するように修正しました。
    
    また、デストラクタではpHogeがNULLでないときdeleteするようにしました。


引用返信 編集キー/
■95399 / inTopicNo.4)  Re[3]: C++ サイズ0でnewした場合
□投稿者/ 774RR (814回)-(2020/07/28(Tue) 16:33:06)
うん? その修正は何か的外れな気がする。

コンストラクタ内で T* p=new T[n]; した結果(は普通には nullptr にはならない)を
デストラクタ内で delete[] p; する分には何一つ規格違反していないしメモリリークしてないよ
たとえ n==0 であっても

n=0; のとき T* p = new T[n]; した結果を if (n!=0) delete[] p; しているのだとしたら
それはメモリリークだけど。

delete nullptr や delete[] nullptr は「何もしない」と決まっているので
JIS X 3014:2003 5.3.5 の 2
delete[] 前の NULL チェックもいらない。

おかしい方向に舵を切る前に本当に発生している事象の正しい確認が必要そう。

引用返信 編集キー/
■95409 / inTopicNo.5)  Re[4]: C++ サイズ0でnewした場合
□投稿者/ az (13回)-(2020/07/29(Wed) 11:47:31)
2020/07/29(Wed) 11:47:59 編集(投稿者)
No95399 (774RR さん) に返信
説明が不足しており、すみません。

デストラクタで 

if (n>0)
{
    if(p != NULL)
    {
        delete[] p;
    } 
}

しています。


そもそも、n=0の時はnewする必要がない(参照しない)ため、
n<=0の時は(newしない、deleteもしない)という修正方法にしました。(Hoge* p = NULLのまま)


もう一つ、確認させていただきたいのですが、
Visual Studio 2010にて割り付けられるオーバーヘッドのサイズ、仕様について記載されているところはありますでしょうか?
Microsoftのヘルプ(new)を確認したのですが、そのような記述を見つけられなかったため、ご存じでしたら教えてください。

引用返信 編集キー/
■95410 / inTopicNo.6)  Re[5]: C++ サイズ0でnewした場合
□投稿者/ 774RR (815回)-(2020/07/29(Wed) 13:11:31)
new T[0] も delete[] nullptr; も完全に正当だし、オイラならその修正案は採用しないかな。
new T[0] で消費するメモリ量が実機で問題になるほどなら別だけど(実際問題としてありえない)

オーバーヘッドのオフィシャルな文書ってことだと見たことないな
$(VS20**)/VC/Tools/MSVC/version/crt/src/vcruntime 以下に new のソースがあるっぽいよ
# VS2019 で確認 VS2010 は手元にないのでわからない

デバッグモードとリリースモードではヒープ破壊検出のための無駄領域があったりなかったりするし
境界整合値とか L1 キャッシュ境界とかがコンパイルオプションで変わるはず。
なので具体的に何バイトってのは簡単にはわからない。
特定状況に限定するなら調べる価値はありそうだけど、手間の割に得られるものが少なすぎ:
オイラならそこんところは手を付けない。

何回 new T[0] したら何バイトを消費してプライベートワーキングセットがどれだけ増えるか?
を気にしてるんだろうけど、仮想記憶機構の配下では勝手にスワップされるわけだし無意味っぽい。
性能落ちを気にするのなら最初から new をしないように作るべきだ。

引用返信 編集キー/
■95414 / inTopicNo.7)  Re[6]: C++ サイズ0でnewした場合
□投稿者/ 774RR (816回)-(2020/07/29(Wed) 15:41:29)
#include <iostream>
#include <vector>
#include <conio.h>

#define ONETESTSIZE 1000000
#define REPEATURE 10
#define ALLOCTESTSIZE ONETESTSIZE*REPEATURE

void testfunc(std::vector<int*>& v) {
	for (int i = 0; i < ONETESTSIZE; ++i) {
		v.push_back(new int[0]);
	}
	std::cout << v.size() << std::endl;
	_getch();
}

int main()
{
	std::vector<int*> a;
	a.reserve(ALLOCTESTSIZE);
	for (int i = 0; i < REPEATURE; ++i) {
		testfunc(a);
	}
	return 0;
}

を VS2019 x86 Release と Debug で両方試してみた(コンパイルオプションは標準値)

100万回の new int[0] で
Release だと 19.6MiB プライベートワーキングセットが増える
Debug だと 46MiB 前後プライベートワーキングセットが増える
1回あたりだと Release=20byte Debug=48byte ってとこだろうか?
これを多いとみるか少ないとみるかは人それぞれだね


引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ