デフォルトでポインタがNULLで初期化されないのはなぜですか?


118

ポインタが初期化されない理由を誰かが説明できますかNULL
例:

  void test(){
     char *buf;
     if (!buf)
        // whatever
  }

if bufがnullでないため、プログラムはif内にステップインしません。

なぜゴミ箱をオンにした変数、特にメモリ上のゴミ箱を指すポインタが必要なのですか?


13
まあ、基本的な型は初期化されていないためです。だから私はあなたの「本当の」質問を仮定します:なぜ基本的な型が初期化されないのですか?
GManNickG

11
「bufがnullではないため、プログラムはif内にステップインしません。」不正解です。あなたはbuf 何であるかを知らないので、それ何でないかを知ることはできません
Drew Dormann、

Javaのようなものとは対照的に、C ++は開発者に多くの責任を与えます。
リシ14

()コンストラクターを使用する場合、整数、ポインター、デフォルトは0です。
Erik Aronesty 2015年

C ++を使用している人が何をしているのかを知っているという前提のため、さらに、スマートポインター上で生のポインターを使用している人は、彼らが何をしているのかを知っています。
ロフティライオン

回答:


161

ポインター(および他のPODタイプ)を初期化する必要があることは誰でも知っています。
次に、問題は「誰が初期化するか」になります。

基本的に2つの方法があります。

  • コンパイラはそれらを初期化します。
  • 開発者はそれらを初期化します。

コンパイラが開発者によって明示的に初期化されていない変数を初期化したと仮定します。次に、変数の初期化が簡単ではない状況に遭遇し、開発者が宣言ポイントでそれを行わなかった理由は、何らかの操作を実行してから割り当てる必要があったためです。

これで、変数をNULLに初期化するコードにコンパイラーが追加の命令を追加し、その後、正しい初期化を行うための開発者コードが追加されたという状況になりました。または、他の条件下では、変数は使用されない可能性があります。多くのC ++開発者は、両方の条件下でその追加の指示を犠牲にして反則を叫ぶでしょう。

ちょうど時間ではありません。しかし、スペースも。両方のリソースが非常に貴重で、開発者も諦めたくない環境がたくさんあります。

BUT:初期化を強制する効果をシミュレートできます。ほとんどのコンパイラは、初期化されていない変数について警告します。したがって、私は常に警告レベルを可能な限り最高のレベルに変えます。次に、すべての警告をエラーとして処理するようコンパイラーに指示します。これらの条件下では、ほとんどのコンパイラーは初期化されていないが使用されている変数に対してエラーを生成するため、コードが生成されなくなります。


5
Bob Tabor氏は、「初期化を十分に考慮していない人が多すぎます!」すべての変数を自動的に初期化するのは「やさしい」ですが、時間がかかり、遅いプログラムは「不親切」です。見つかったランダムなガベージmallocを示したスプレッドシートまたはエディターは受け入れられません。C、訓練されたユーザー向けの鋭いツール(誤用すると危険)は、自動変数の初期化に時間をかけるべきではありません。変数を初期化するためのトレーニングホイールマクロも考えられますが、多くの場合、立ち上がって、注意して、少し出血する方がよいと考えています。ピンチで、あなたはあなたが練習する方法で働きます。だから、あなたがそうするように練習してください。
Bill IV、

2
初期化をすべて修正した人だけが回避できるバグの数に驚くでしょう。コンパイラの警告がなければ、これは退屈な作業になります。
ジョナサンヘンソン

4
@ロキ、私はあなたの主張に従うのに苦労しています。私はあなたの答えが役に立ったと賞賛しようとしたところです。そうでなければ申し訳ありません。
ジョナサンヘンソン

3
ポインタが最初にNULLに設定され、次に任意の値に設定されている場合、コンパイラはこれを検出して最初のNULL初期化を最適化できるはずですよね?
Korchkidu 2014

1
@Korchkidu:時々。ただし、大きな問題の1つは、デフォルトが使用に最適でないことを認識できないため、初期化を忘れたことを警告する方法がないことです。
デデュプリケータ、2015年

41

TC ++ PLのBjarne Stroustrupの引用(特別版p.22):

機能の実装は、それを必要としないプログラムに大きなオーバーヘッドを課すべきではありません。


オプションも与えないでください。思われる
ジョナサン

8
@ Jonathanは、nullへのポインタまたはC ++の標準である0へのポインタの初期化を妨げるものはありません。
stefanB 2009

8
はい、ただしStroustrupは、ポインタをゼロで初期化することにより、デフォルトの構文でパフォーマンスよりもプログラムの正確さを優先し、プログラマが明示的にポインタを初期化しないように要求するようにできました。結局のところ、ほとんどの人は、プログラム全体のバグを修正するよりも少量のコードを最適化する方が一般に簡単であるという理由で、高速だが間違っているよりも正しいが遅いを好む。特に、その多くがまともなコンパイラで実行できる場合。
Robert Tuck

1
互換性を損なうことはありません。このアイデアは、「int * x = __uninitialized」と組み合わせて検討されています-デフォルトで安全、意図による速度。
MSalters 2009

4
私は何Dが好きですか。初期化したくない場合は、この構文float f = void;またはを使用してくださいint* ptr = void;。現在はデフォルトで初期化されていますが、本当に必要な場合は、コンパイラーによる実行を停止できます。
deft_code

23

初期化には時間がかかるからです。そしてC ++では、変数で最初に行うべきことは、明示的に初期化することです。

int * p = & some_int;

または:

int * p = 0;

または:

class A {
   public:
     A() : p( 0 ) {}  // initialise via constructor
   private:
     int * p;
};

1
k、初期化に時間がかかり、それでもそれが必要な場合、手動で設定せずにポインターをnullにするにはどうすればよいですか?ほら、私はそれを修正したくないからではない。なぜなら、アドレスにゴミが付いた統合化されたポインタを使用することは決してないようだから
ジョナサン

1
クラスのコンストラクターでクラスメンバーを初期化します。これがC ++の動作方法です。

3
@ジョナサン:nullもゴミです。nullポインターを使用して、有用なことを行うことはできません。どちらかを逆参照するのも同じくらいエラーです。nullではなく、適切な値でポインタを作成します。
DrPizza 2009

2
ポインタをNnullに初期化することは賢明なことです。nullポインタに対して実行できる操作がいくつかあります。それらをテストして、それらに対してdeleteを呼び出すことができます。

4
明示的に初期化せずにポインターを使用する予定がない場合は、値を指定する前にポインターに何が含まれているかは関係ありません。CおよびC ++の原則では、使用した分だけ支払いますが、それは行われません。自動的に。受け入れ可能なデフォルト値(通常はNULLポインター)がある場合は、初期化する必要があります。初期化することも、初期化しないでおくこともできます。
David Thornley、

20

C ++のモットーの1つは:


使用しないものについては支払いません


このため、たとえばoperator[]vectorクラスのはインデックスが範囲外かどうかをチェックしません。


12

歴史的な理由から、これは主にCで行われているためです。なぜCでこのように行われるのかは別の問題ですが、この設計上の決定にはオーバーヘッドゼロの原則が何らかの形で関与していたと思います。


Cはメモリへのアクセスが容易な低レベル言語(別名ポインタ)と見なされているため、自由に好きなことを実行でき、すべてを初期化することでオーバーヘッドを課すことはありません。ところで私は、私はすべての変数が0に設定されますので、使用前に0にすべてのそれのメモリを初期化するモバイルプラットフォームベースのLinux上で働いていたので、それはプラットフォームに依存だと思う
stefanB

8

その上、あなたがそれを吹くときの警告があります:あなたのコンパイラーに応じて、「値が割り当てられる前に使用される可能性があります」または同様の表現。

警告付きでコンパイルしますよね?


また、コンパイラのトレースに問題がある可能性があることを確認するだけで可能です。
Deduplicator

6

変数が初期化されていないことが理にかなっている状況はほとんどありませんが、デフォルトの初期化のコストは小さいので、なぜそれを行うのですか?

C ++はC89ではありません。地獄、CでさえC89ではありません。宣言とコードを混在させることができるため、初期化に適した値が得られるまで宣言を延期する必要があります。


2
次に、すべての値を2回書き込む必要があります。1回はコンパイラのセットアップルーチンによって、もう1回はユーザーのプログラムによってです。通常は大きな問題ではありませんが、それが加算されます(たとえば、100万項目の配列を作成している場合)。自動初期化が必要な場合は、常にそれを行う独自のタイプを作成できます。しかし、この方法では、不要なオーバーヘッドを受け入れざるを得なくなります。
Jeremy Friesner、2009

3

ポインタは別のタイプです。intcharまたは他のPODタイプを作成した場合、それはゼロに初期化されないので、なぜポインターが必要なのでしょうか?これは、このようなプログラムを作成する人にとっては不要なオーバーヘッドと考えることができます。

char* pBuf;
if (condition)
{
    pBuf = new char[50];
}
else
{
    pBuf = m_myMember->buf();
}

初期化することがわかっている場合pBuf、メソッドの上部で最初に作成するときにプログラムにコストがかかるのはなぜですか?これはオーバーヘッドゼロの原則です。


1
一方、char * pBuf = condition?新しいchar [50]:m_myMember-> buf(); 構文よりも効率の方が似ていますが、それでも私はあなたに同意します。
the_drow 2009

1
@the_drow:まあ、それをもっと複雑にすることができるので、そのような書き換えは不可能です。
Deduplicator

2

常にNULLに初期化されるポインターが必要な場合は、C ++テンプレートを使用してその機能をエミュレートできます。

template<typename T> class InitializedPointer
{
public:
    typedef T       TObj;
    typedef TObj    *PObj;
protected:
    PObj        m_pPointer;

public:
    // Constructors / Destructor
    inline InitializedPointer() { m_pPointer=0; }
    inline InitializedPointer(PObj InPointer) { m_pPointer = InPointer; }
    inline InitializedPointer(const InitializedPointer& oCopy)
    { m_pPointer = oCopy.m_pPointer; }
    inline ~InitializedPointer() { m_pPointer=0; }

    inline PObj GetPointer() const  { return (m_pPointer); }
    inline void SetPointer(PObj InPtr)  { m_pPointer = InPtr; }

    // Operator Overloads
    inline InitializedPointer& operator = (PObj InPtr)
    { SetPointer(InPtr); return(*this); }
    inline InitializedPointer& operator = (const InitializedPointer& InPtr)
    { SetPointer(InPtr.m_pPointer); return(*this); }
    inline PObj operator ->() const { return (m_pPointer); }
    inline TObj &operator *() const { return (*m_pPointer); }

    inline bool operator!=(PObj pOther) const
    { return(m_pPointer!=pOther); }
    inline bool operator==(PObj pOther) const
    { return(m_pPointer==pOther); }
    inline bool operator!=(const InitializedPointer& InPtr) const
    { return(m_pPointer!=InPtr.m_pPointer); }
    inline bool operator==(const InitializedPointer& InPtr) const
    { return(m_pPointer==InPtr.m_pPointer); }

    inline bool operator<=(PObj pOther) const
    { return(m_pPointer<=pOther); }
    inline bool operator>=(PObj pOther) const
    { return(m_pPointer>=pOther); }
    inline bool operator<=(const InitializedPointer& InPtr) const
    { return(m_pPointer<=InPtr.m_pPointer); }
    inline bool operator>=(const InitializedPointer& InPtr) const
    { return(m_pPointer>=InPtr.m_pPointer); }

    inline bool operator<(PObj pOther) const
    { return(m_pPointer<pOther); }
    inline bool operator>(PObj pOther) const
    { return(m_pPointer>pOther); }
    inline bool operator<(const InitializedPointer& InPtr) const
    { return(m_pPointer<InPtr.m_pPointer); }
    inline bool operator>(const InitializedPointer& InPtr) const
    { return(m_pPointer>InPtr.m_pPointer); }
};

1
これを実装している場合は、コピークターや割り当てopを気にする必要はありません。デフォルトで十分です。そして、あなたのデストラクタは無意味です。もちろん、以下の演算子を使用してポインターをテストすることもできます(状況によっては)すべて提供する必要があります。

OK、実装するのは簡単ではありません。オブジェクトがスコープ外(つまり、関数のサブスコープ内で定義されたローカル)になった場合でもスタックのスペースを使用している場合、メモリがガベージへのぶら下がりポインタとして残されないように、デストラクタを用意しました。しかし、まじめに、私はこれを5分未満で書きました。完璧であるという意味ではありません。
Adisak

OKはすべての比較演算子を追加しました。デフォルトのオーバーライドは冗長な場合がありますが、これは例であるため、ここでは明示的にオーバーライドしています。
Adisak

1
手動で設定せずにすべてのポインターをnullにする方法を理解できませんでした。ここで何をしたのか説明していただけますか?
ジョナサン

1
@ジョナサン:これは基本的に、ポインタをnullに設定する以外に何もしない「スマートポインタ」です。IEではなくIE Foo *aを使用InitializedPointer<Foo> a- Foo *a=0タイピングが少ない純粋に学術的な演習。ただし、上記のコードは教育の観点から非常に便利です。(「プレースホルダー」のctor / dtorと代入演算に)少しの変更を加えると、スコープ付きポインター(デストラクターで解放されます)や参照カウントポインター(inc /を追加することなど)を含むさまざまなタイプのスマートポインターに簡単に拡張できます。 m_pPointerが設定またはクリアされたときのdec操作。
Adisak

2

静的データは0に初期化されることに注意してください(別の言い方をしない限り)。

そして、はい、常に変数をできるだけ遅く、初期値で宣言する必要があります。のようなコード

int j;
char *foo;

それを読んだときに警報ベルをオフに設定する必要があります。それは100%合法なので、糸くずがそれについて鯉に説得できるかどうかはわかりません。


それは保証されていますか、それとも今日のコンパイラで使用されている一般的な方法ですか?
gha.st 2009

1
静的変数は0に初期化されます。これは、ポインターに対しても正しいことを行います(つまり、すべてのビット0ではなくNULLに設定します)。この動作は標準で保証されています。
Alok Singhal、

1
静的データのゼロへの初期化は、CおよびC ++標準によって保証されています。これは、単なる一般的な方法ではありません
groovingandi

1
おそらく一部の人々が自分のスタックがうまく整列されていることを確認したいので、関数の上部ですべての変数を事前宣言しますか?多分彼らはこれを必要とする交流方言で書いていますか?
KitsuneYMG 2009

1

考えられるもう1つの理由は、リンク時にポインターにアドレスが与えられますが、ポインターの間接的なアドレス指定/逆参照はプログラマーの責任です。たいていの場合、コンパイラーはそれほど気にしませんが、ポインターを管理し、メモリーリークが発生しないようにするために、プログラマーに負担がかかります。

簡単に言えば、リンク時にポインタ変数にアドレスが与えられるという意味で、これらは初期化されます。上記のコード例では、SIGSEGVがクラッシュまたは生成することが保証されています。

健全性のために、ポインターをNULLに初期化します。これは、プログラムを誤動作させない理由をプログラマーに知らせない、mallocまたは newプログラマーの手がかりとなる場合に、ポインターを逆参照しようとする場合です。

これが役に立って意味があるといいのですが、


0

まあ、C ++がポインタを初期化した場合、「C ++はCよりも遅い」と不平を言うCの人々は、何かにぶつかるはずです。


それは私の理由ではありません。私の理由は、ハードウェアに512バイトのROMと128バイトのRAMがあり、ゼロを追加するための追加の命令が1バイトであっても、プログラム全体のかなりの割合であることです。そのバイトが必要です!
ジェリージェレミア

0

C ++はCのバックグラウンドから来ています-これから戻ってくるいくつかの理由があります:

Cは、C ++よりも、アセンブリ言語の置き換えです。指示されていないことは何もしません。そのため:あなたがそれを無効にしたいなら-それをしてください!

また、Cのようなベアメタル言語でnullの場合、一貫性に関する質問が自動的に表示されます。mallocを実行すると、自動的にゼロに設定されますか?スタック上に作成された構造体はどうですか?すべてのバイトをゼロにする必要がありますか?グローバル変数はどうですか?「(* 0x18);」のようなステートメントについてはどうでしょうか。これは、メモリ位置0x18をゼロにする必要があることを意味していませんか?


実際、Cでは、すべて0のメモリを割り当てる場合は、を使用できますcalloc()
David Thornley、

1
私の要点-あなたがそれをしたいなら、それはできますが、それはあなたのために自動的に行われません
gha.st

0

あなたが話しているこれらの指針は何ですか?

例外安全のため、必ず使用しauto_ptrshared_ptrweak_ptrおよびその他の変異体。
優れたコードの特徴は、への1回の呼び出しを含まないコードですdelete


3
C ++ 11以降、回避auto_ptrして置き換えunique_ptrます。
Deduplicator

-2

ああ少年。本当の答えは、メモリをゼロにするのは簡単です。これは、たとえばポインタの基本的な初期化です。これは、オブジェクト自体の初期化とは何の関係もありません。

ほとんどのコンパイラーが最高レベルで与える警告を考えると、最高レベルのプログラミングをエラーとして扱うことは想像できません。それらを上げても、生成された大量のコードの1つでもバグを救うことができなかったので、これをお勧めすることはできません。


ポインターがであると予期されていない場合、NULLそれを初期化することは同じくらいエラーになります。
デデュプリケータ、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.