Почему указатели не инициализируются с помощью NULL по умолчанию?


может кто-нибудь объяснить, почему указатели не инициализируются в NULL?
Пример:

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

программа не будет входить в if, потому что buf не null.

Я хотел бы знать, почему, в каком случае нам нужна переменная с мусором, специально указатели, адресующие мусор в памяти?

15 101

15 ответов:

мы все понимаем, что указатель (и другие типы POD) должны быть инициализированы.
Тогда возникает вопрос, кто должен инициализировать их.

Ну есть в основном два метода:

  • компилятор инициализирует их.
  • разработчик инициализирует их.

предположим, что компилятор инициализировал любую переменную, явно не инициализированную разработчиком. Затем мы сталкиваемся с ситуациями, когда инициализация переменной было нетривиально, и причина, по которой разработчик не сделал этого в точке объявления, заключалась в том, что ему нужно было выполнить некоторую операцию, а затем назначить.

Итак, теперь у нас есть ситуация, когда компилятор добавил дополнительную инструкцию к коду, который инициализирует переменную в NULL, а затем код разработчика добавляется для правильной инициализации. Или при других условиях переменная потенциально никогда не используется. Многие разработчики C++ будут кричать фол при обоих условиях на стоимость этой дополнительной инструкции.

дело не только во времени. Но и пространство тоже. Есть много сред, где оба ресурса находятся в премиум-классе, и разработчики не хотят сдаваться.

но: вы можете имитировать эффект принудительной инициализации. Большинство компиляторов предупредит вас о неинициализированных переменных. Поэтому я всегда поворачиваю свой уровень предупреждения на самый высокий уровень. Затем попросите компилятор рассматривать все предупреждения как ошибки. Под эти условия большинство компиляторов затем генерируют ошибку для переменных, которые не инициализированы, но используются, и таким образом предотвращают создание кода.

цитируя Бьярне Страуструпа в TC++PL (Special Edition p. 22):

реализация функции не должна накладывать значительные накладные расходы на программы, которые этого не требуют.

потому что инициализация занимает много времени. И в C++, Самое первое, что вы должны сделать с любой переменной, явно инициализировать его:

int * p = & some_int;

или:

int * p = 0;

или:

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

потому что одним из столпов C++ является:


вы не платите за то, что вам не нужно


именно по этой причине operator[] на не проверяет, находится ли индекс вне границ.

по историческим причинам, главным образом потому, что это так делается в C. Почему это делается так в C, это другой вопрос, но я думаю, что нулевой принцип накладных расходов был как-то вовлечен в это проектное решение.

кроме того, у нас есть предупреждение, когда вы его выдуваете: "возможно, используется до присвоения значения" или аналогичного глагола в зависимости от вашего компилятора.

вы компилируете с предупреждениями, верно?

есть исчезающе мало ситуаций, в которых когда-либо имеет смысл неинициализировать переменную, а инициализация по умолчанию имеет небольшую стоимость, так почему же это делать?

C++ - это не C89. Черт, даже C-это не C89. Вы можете смешивать объявления и код, поэтому вы должны отложить объявление до тех пор, пока у вас не будет подходящего значения для инициализации.

указатель-это просто другой тип. Если вы создадите int,char или любой другой тип POD он не инициализируется до нуля, так почему указатель? Это может считаться ненужными накладными расходами для тех, кто пишет такую программу.

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

если вы знаете, что вы собираетесь инициализировать его, почему должен нести расходы при создании pBuf в верхней части метода? Это принцип нулевых накладных расходов.

Если вы хотите указатель, который всегда инициализируется в 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); }
};

обратите внимание, что статические данные инициализируются до 0 (если не указано иное).

и да, вы всегда должны объявлять свои переменные как можно позже и с начальным значением. Код типа

int j;
char *foo;

должен установить тревожные звонки, когда вы читаете его. Я не знаю, есть ли Линтаs можно убедить придраться к этому, хотя, поскольку это 100% законно.

Ну, если бы C++ инициализировал указатели, то люди C, жалующиеся "C++ медленнее, чем C", имели бы что-то реальное, чтобы держаться ;)

еще одна возможная причина, почему, является то, что во время ссылки указатели задаются адрес, но косвенная адресация/де-ссылки указатель является ответственностью программиста. Как правило, компилятор не заботится меньше, но бремя передается программисту для управления указателями и обеспечения отсутствия утечек памяти.

действительно, в двух словах, они инициализируются в том смысле, что во время ссылки переменной указателя присваивается адрес. В пример кода выше, который гарантированно аварийно завершит работу или сгенерирует SIGSEGV.

ради здравомыслия всегда инициализируйте указатели на NULL, таким образом, если любая попытка разыменовать его без malloc или new подскажет программисту причину, по которой программа вела себя неправильно.

надеюсь, что это поможет и имеет смысл, С уважением, Том.

C++ исходит из фона C - и есть несколько причин, возвращающихся из этого:

C, даже больше, чем C++ является заменой языка ассемблера. Он не делает ничего, что вы не говорите ему делать. Поэтому: если вы хотите обнулить его-сделайте это!

кроме того, если вы обнуляете вещи на голом металлическом языке, например C, автоматически возникают вопросы о согласованности: если вы что-то malloc - он должен автоматически обнуляться? Как насчет структуры, созданной в стеке? все должны байты обнуляются? А как насчет глобальных переменных? как насчет утверждения типа" (*0x18); " не означает ли это, что позиция памяти 0x18 должна быть обнулена?

о каких указателях вы говорите?

для безопасности исключения, всегда используйте auto_ptr,shared_ptr,weak_ptr и другие варианты.
Отличительной чертой хорошего кода является тот, который не включает в себя ни одного вызова delete.

О боже. Реальный ответ заключается в том, что легко обнулить память, что является базовой инициализацией для указателя. Что также не имеет ничего общего с инициализацией самого объекта.

учитывая предупреждения, которые большинство компиляторов дают на самых высоких уровнях, я не могу представить себе программирование на самом высоком уровне и рассматривать их как ошибки. Поскольку их включение никогда не спасало меня даже от одной ошибки в огромном количестве кода, я не могу рекомендовать это.