为什么默认情况下不将指针初始化为NULL?


118

有人可以解释为什么指针没有初始化为NULL吗?
例:

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

该程序不会进入if的内部,因为buf它不为null。

我想知道为什么在什么情况下我们需要一个带有垃圾桶的变量,特别是指向内存中垃圾桶的指针?


13
好吧,因为基本类型未初始化。因此,我假设您的“真实”问题是:为什么不初始化基本类型?
GManNickG

11
“由于buf不为null,因此程序不会进入if”。那是不对的。因为您不知道buf 什么,所以您不知道它不是什么。
德鲁·多曼

与Java相比,C ++给开发人员带来了更多的责任。
Rishi 2014年

整数,指针,如果使用()构造函数,则默认为0。
Erik Aronesty 2015年

因为假设使用C ++的人知道自己在做什么,而且使用原始指针而不是智能指针的某人知道(甚至更清楚)他们在做什么!
崇高的狮子

Answers:


161

我们都意识到应该初始化指针(和其他POD类型)。
然后问题变成“谁应该初始化它们”。

好吧,基本上有两种方法:

  • 编译器将它们初始化。
  • 开发人员将其初始化。

让我们假设编译器初始化了开发人员未显式初始化的任何变量。然后,我们遇到了这样的情况:初始化变量不是一件容易的事,而开发人员未在声明点执行此操作的原因是,他/她需要执行一些操作然后进行赋值。

因此,现在的情况是,编译器已向代码中添加了一条额外的指令,以将变量初始化为NULL,然后再添加开发人员代码以进行正确的初始化。或者在其他条件下,该变量可能永远不会使用。在这两种情况下,很多C ++开发人员都会以额外的指令为代价大喊大叫。

这不只是时间。而且还有空间。在很多环境中,两种资源都很宝贵,开发人员也不愿放弃任何一种资源。

但是:您可以模拟强制初始化的效果。大多数编译器会警告您未初始化的变量。因此,我总是将警告级别尽可能地提高。然后告诉编译器将所有警告视为错误。在这种情况下,大多数编译器将为未初始化但已使用的变量生成错误,从而阻止生成代码。


5
鲍勃·塔博尔(Bob Tabor)说:“太多的人没有对初始化进行充分的考虑!”它“友好”地自动初始化所​​有变量,但是这需要时间,并且慢速程序是“不友好的”。显示已找到随机垃圾malloc的电子表格或编辑器将是不可接受的。C,对于训练有素的用户而言,这是一种锋利的工具(如果使用不当,将会很危险),无需花时间初始化自动变量。可以使用训练轮宏来初始化变量,但是许多人认为站起来,注意和流血会更好。紧要关头,您将按照自己的方式工作。所以练习吧。
Bill IV

2
您会惊讶地发现,只要有人修复所有的初始化,就会避免多少错误。如果不是针对编译器警告,这将是繁琐的工作。
乔纳森·亨森

4
@Loki,我很难理解你的观点。我只是想赞扬您的回答是有帮助的,希望您能总结一下。如果没有,我很抱歉。
乔纳森·亨森

3
如果先将指针设置为NULL,然后再设置为任何值,则编译器应该能够检测到该指针并优化第一个NULL初始化,对吗?
Korchkidu 2014年

1
@Korchkidu:有时候。不过,主要问题之一是它无法警告您忘记进行初始化,因为它不知道默认设置对您的使用并不完美。
Deduplicator 2015年

41

在TC ++ PL(特别版p.22)中引用Bjarne Stroustrup:

功能的实现不应在不需要该功能的程序上增加大量开销。


也不给出任何选择。看来
乔纳森(Jonathan)2009年

8
@ Jonathan没有什么阻止您初始化指向null或C ++中标准的0的指针。
stefanB

8
是的,但是Stroustrup可以通过将指针归零来使默认语法有利于程序的正确性,而不是提高性能,并且使程序员不得不明确地要求将指针初始化。毕竟,大多数人更喜欢正确但缓慢而不是快速但错误,因为通常,优化少量代码比修复整个程序中的错误更容易。尤其是当许多事情可以由一个不错的编译器完成时。
罗伯特·塔克

1
它不会破坏兼容性。该想法已与“ int * x = __uninitialized”(int * x = __未初始化)结合使用-默认情况下是安全的,意图是速度。
MSalters

4
我喜欢D。如果您不想初始化,请使用此语法float f = void;int* ptr = void;。现在默认情况下已将其初始化,但如果确实需要,则可以停止编译器执行此操作。
deft_code 2010年

23

因为初始化需要时间。在C ++中,对任何变量都应该做的第一件事就是显式地初始化它:

int * p = & some_int;

要么:

int * p = 0;

要么:

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

1
k,如果初始化需要时间并且我仍然想要它,是否会在没有手动设置的情况下使指针变为空?看,不是因为我不想纠正它,因为我似乎永远不会在地址上使用带有垃圾桶的联合指针
Jonathan

1
您可以在类的构造函数中初始化类成员-这就是C ++的工作方式。

3
@乔纳森:但null也是垃圾。使用空指针不能做任何有用的事情。取消引用一个也是一样的错误。用正确的值创建指针,而不是空值。
DrPizza

2
将指针初始化为Nnull可能是明智的做法,并且您可以对空指针执行多种操作-您可以对其进行测试并可以对它们调用delete。

4
如果您永远都不会在没有显式初始化的情况下使用指针,那么在给它赋值之前它所包含的内容并不重要,并且按照C和C ++的原则,只为所用内容付费自动。如果存在可接受的默认值(通常为空指针),则应将其初始化。您可以选择初始化或不初始化。
David Thornley,2009年

20

因为C ++的座右铭之一是:


您不用为不使用的东西付费


因此,此类的operator[]of vector不会检查索引是否超出范围。


12

出于历史原因,主要是因为这是在C中完成的方式。为什么要像在C中那样完成,这是另一个问题,但是我认为零开销原则在某种程度上涉及了此设计决策。


我猜是因为C被认为是低级语言,可轻松访问内存(又称指针),所以它使您可以自由地执行所需的操作,并且不会因初始化所有内容而增加开销。顺便说一句,我认为这取决于平台,因为我在基于Linux的移动平台上工作,该平台在使用前将其所有内存初始化为0,因此所有变量都将设置为
0。– stefanB

8

此外,我们确实会警告您何时删除它:“可能在分配值之前使用”或类似的语言,具体取决于您的编译器。

您确实会编译警告,对吗?


并且有可能承认编译器跟踪可能有错误。
Deduplicator

6

在几乎没有什么情况下,对变量进行未初始化是有意义的,而缺省初始化的代价很小,那为什么要这么做呢?

C ++不是C89。天哪,即使C也不是C89。您可以混合使用声明和代码,因此应将声明推迟到具有合适的值进行初始化的时间。


2
然后,只需将每个值写入两次-一次是通过编译器的设置例程,一次是通过用户程序。通常这不是一个大问题,但它会加起来(例如,如果要创建一百万个项目的数组)。如果要进行自动初始化,则始终可以创建自己的类型进行初始化。但这样一来,您就不必被迫接受不必要的开销。
杰里米·弗里斯纳

3

指针只是另一种类型。如果你创建一个intchar或任何其他类型的POD它不初始化为零,所以为什么要一个指针?对于这样编写程序的人来说,这可能被认为是不必要的开销。

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

如果您知道要对其进行初始化,那么当您第一次pBuf在该方法的顶部创建该程序时,为什么该程序会产生成本?这是零开销原则。


1
另一方面,您可以执行char * pBuf = condition吗?新字符[50]:m_myMember-> buf(); 这更像是语法问题,然后是效率问题,但我还是同意你的看法。
the_drow

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
如果执行此操作,则不会为复制ctor或分配操作烦恼-默认值是可以的。而且您的析构函数毫无意义。当然,在某些情况下,您还可以使用小于操作符等来测试指针,因此应提供它们。

好的,实施起来并不容易。我使用了析构函数,因此,如果对象超出范围(即,在函数的子作用域内定义的局部对象)但仍在堆栈上占用空间,则不会将内存留作悬空的垃圾指针。但是伙计,我认真地写了这篇文章,不到5分钟。这并不意味着是完美的。
Adisak

确定添加所有比较运算符。默认替代可能是多余的,但由于此处是示例,因此在此明确指定。
Adisak

1
我不明白如何在不手动设置所有指针的情况下使所有指针为空,请您解释一下您在这里所做的工作吗?
乔纳森(Jonathan)在2009年

1
@乔纳森:这基本上是一个“智能指针”,除了将指针设置为null外,什么也没有做。使用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,这对于指针也做正确的事情(即,将它们设置为NULL,而不是全部设置为0)。该行为由标准保证。
Alok Singhal,2009年

1
C和C ++标准保证将静态数据初始化为零,这不仅仅是常见的做法
groovingandi

1
也许是因为某些人想确保其堆栈对齐良好,所以他们在函数顶部预先声明了所有变量?也许他们在用交流方言写作,要求这样做?
KitsuneYMG

1

另一个可能的原因是,在链接时给了指针一个地址,但是间接寻址/取消引用指针是程序员的责任。通常,编译器不会在乎,但是负担会转移到程序员那里来管理指针并确保不会发生内存泄漏。

简而言之,实际上是在链接时为指针变量指定了地址的意义上初始化它们的。在上面的示例代码中,这肯定会崩溃或生成SIGSEGV。

为了保持理智,请始终将指向NULL的指针初始化,以这种方式在没有引用的情况下尝试取消引用,malloc否则 new会提示程序员导致程序行为异常的原因。

希望这会有所帮助并且有意义,


0

好吧,如果C ++确实初始化了指针,那么C人士抱怨“ C ++比C慢”,这确实有待解决;)


那不是我的理由 我的理由是,如果硬件具有512字节的ROM和128字节的RAM,并且额外的指令将零置零,则指针甚至是一个字节,占整个程序的很大一部分。我需要那个字节!
杰里·耶利米

0

C ++来自C的背景-并且有一些原因会导致这种情况:

C,甚至比C ++更是汇编语言的替代品。它不会做任何您不要求做的事情。因此:如果您想将其为NULL,请执行此操作!

此外,如果您使用C之类的裸机语言为空,则会自动出现一致性问题:如果您分配了某些内容-应该自动将其清零吗?那么在堆栈上创建的结构呢?是否所有字节都应清零?全局变量呢?像“(** 0x18);”这样的语句呢?那不是意味着存储器位置0x18应该清零吗?


实际上,在C语言中,如果要分配全零内存,则可以使用calloc()
David Thornley,2009年

1
只是我的意思-如果您想这样做,可以,但是并不能自动完成
gha.st

0

您所说的这些指针是什么?

对于异常安全,始终使用auto_ptrshared_ptrweak_ptr和他们的其他变体。
好的代码的标志是不包含对的单个调用delete


3
从C ++ 11开始,请避免auto_ptr和替代unique_ptr
Deduplicator 2015年

-2

好家伙。真正的答案是很容易将内存清零,这是指针的基本初始化。这也与初始化对象本身无关。

考虑到大多数编译器在最高级别给出的警告,我无法想象在最高级别进行编程并将其视为错误。由于将它们调高甚至从来没有为我节省过大量代码生成中的一个错误,因此我不推荐这样做。


如果指针不被预期NULL,它初始化到是一样多的错误。
Deduplicator
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.