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

118

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

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

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

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

Джонатан
источник
13
Ну, потому что основные типы остались неинициализированными. Итак, я предполагаю ваш «настоящий» вопрос: почему не инициализируются фундаментальные типы?
GManNickG
11
"программа не войдет в if, потому что buf не равен нулю". Это не так. Так как вы не знаете , что ЬеЕ это , вы не можете знать , что это не .
Дрю Дорманн,
В отличие от чего-то вроде Java, C ++ возлагает на разработчика гораздо больше ответственности.
Риши
целые числа, указатели, по умолчанию 0, если вы используете конструктор ().
Эрик Аронести,
Из-за предположения, что кто-то, использующий C ++, знает, что они делают, более того, тот, кто использует необработанные указатели вместо интеллектуального, знает (даже более того), что они делают!
Lofty Lion

Ответы:

161

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

Что ж, в основном есть два метода:

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

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

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

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

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

Мартин Йорк
источник
5
Боб Табор сказал: «Слишком много людей не задумывались об инициализации!» Это «удобно», чтобы инициализировать все переменные автоматически, но это требует времени, а медленные программы «недружелюбны». Электронная таблица или редакторы, показывающие обнаруженный случайный мусор, будут неприемлемыми. C, острый инструмент для обученных пользователей (опасный при неправильном использовании), не требует времени на инициализацию автоматических переменных. Макрос обучающего колеса для инициализации переменных мог бы быть, но многие думают, что лучше встать, быть внимательным и немного истечь кровью. В крайнем случае, вы работаете так, как практикуете. Так что практикуйтесь, как хотите.
Bill IV
2
Вы были бы удивлены, узнав, сколько ошибок можно было бы избежать, если бы кто-то исправил всю их инициализацию. Это было бы утомительно, если бы не предупреждения компилятора.
Джонатан Хенсон,
4
@Loki, мне трудно понять твою точку зрения. Я просто пытался оценить ваш ответ как полезный, надеюсь, вы это поняли. Если нет, мне очень жаль.
Джонатан Хенсон,
3
Если указатель сначала устанавливается на NULL, а затем устанавливается на любое значение, компилятор должен быть в состоянии обнаружить это и оптимизировать первую инициализацию NULL, верно?
Корчкиду
1
@Korchkidu: Иногда. Одна из основных проблем заключается в том, что он не может предупредить вас о том, что вы забыли выполнить инициализацию, поскольку он не может знать, что значение по умолчанию не идеально для вашего использования.
Дедупликатор
41

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

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

Джон
источник
и тоже не оставляю выбора. Кажется
Джонатан
8
@ Jonathan ничто не мешает вам инициализировать указатель значением null - или 0, как это принято в C ++.
stefanB
8
Да, но Страуструп мог сделать синтаксис по умолчанию в пользу правильности программы, а не производительности, инициализировав указатель нулями, и заставить программиста явным образом запрашивать неинициализацию указателя. В конце концов, большинство людей предпочитают правильное, но медленное, а не быстрое, но неправильное, на том основании, что обычно легче оптимизировать небольшой объем кода, чем исправлять ошибки во всей программе. Особенно, когда многое из этого можно сделать приличным компилятором.
Роберт Так,
1
Это не нарушает совместимости. Идея была рассмотрена в сочетании с «int * x = __uninitialized» - безопасность по умолчанию, скорость по намерению.
MSalters
4
Мне нравится то, что Dесть. Если вы не хотите инициализации, используйте этот синтаксис float f = void;или int* ptr = void;. Теперь он инициализируется по умолчанию, но если вам действительно нужно, вы можете остановить компилятор от этого.
deft_code 07
23

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

int * p = & some_int;

или:

int * p = 0;

или:

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

источник
1
k, если инициализация требует времени, а я все еще хочу, чтобы мои указатели обнулялись, не задавая их вручную? понимаете, не потому, что я не хочу это исправлять, потому что мне кажется, что я никогда не буду использовать унитилизированные указатели с мусором в их адресах
Джонатан
1
Вы инициализируете члены класса в конструкторе класса - так работает C ++.
3
@ Джонатан: но null - тоже мусор. Вы не можете сделать ничего полезного с нулевым указателем. Разыменование одного из них - такая же ошибка. Создавайте указатели с правильными значениями, а не с нулями.
DrPizza
2
Инициализация апоинтера на Nnull может быть разумной вещью. И есть несколько операций, которые вы можете выполнять с нулевыми указателями - вы можете проверить их и вызвать для них delete.
4
Если вы никогда не собираетесь использовать указатель без его явной инициализации, не имеет значения, что он содержал до того, как вы указали ему значение, а в соответствии с принципом C и C ++ оплаты только за то, что вы используете, это не делается. автоматически. Если есть приемлемое значение по умолчанию (обычно нулевой указатель), вы должны его инициализировать. Вы можете инициализировать его или оставить неинициализированным на ваш выбор.
Дэвид Торнли,
20

Потому что один из девизов C ++:


Вы не платите за то, что не используете


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

KeatsPeeks
источник
12

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

арак
источник
Я предполагаю, потому что C считается языком более низкого уровня с легким доступом к памяти (он же указатели), поэтому он дает вам свободу делать то, что вы хотите, и не накладывает накладные расходы, инициализируя все. Кстати, я думаю, что это зависит от платформы, потому что я работал над мобильной платформой на базе Linux, которая инициализировала всю свою память до 0 перед использованием, поэтому все переменные будут установлены на 0.
stefanB
8

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

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

Джошуа
источник
И это просто возможно как подтверждение того, что трассировка компилятора может быть ошибочной.
Дедупликатор
6

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

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

DrPizza
источник
2
Тогда просто каждое значение нужно будет записать дважды - один раз процедурой установки компилятора, а другой - программой пользователя. Обычно это не большая проблема, но она складывается (например, если вы создаете массив из 1 миллиона элементов). Если вам нужна автоинициализация, вы всегда можете создать свои собственные типы, которые это делают; но таким образом вы не будете вынуждены мириться с ненужными накладными расходами, если вы этого не хотите.
Джереми Фриснер,
3

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

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

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

LeopardSkinPillBoxHat
источник
1
с другой стороны, вы можете сделать char * pBuf = condition? новый символ [50]: m_myMember-> buf (); Это больше похоже на синтаксис, чем на эффективность, но, тем не менее, я с вами согласен.
the_drow
1
@the_drow: Ну, можно сделать его более сложным, просто чтобы такое переписывание было невозможно.
Дедупликатор
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); }
};
Adisak
источник
1
Если бы я реализовал это, я бы не стал беспокоиться о ctor копирования или операции назначения - значения по умолчанию вполне в порядке. И ваш деструктор бессмысленен. Конечно, вы также можете тестировать указатели, используя оператор less и все) в некоторых случаях), поэтому вы должны их предоставить.
Ладно, реализовать менее тривиально. У меня был деструктор, так что если объект выходит за пределы области видимости (то есть локально определен в подподобии функции), но все еще занимает место в стеке, память не остается в качестве висячего указателя на мусор. Но, чувак, серьезно, я написал это менее чем за 5 минут. Это не должно быть идеальным.
Adisak
ОК добавил все операторы сравнения. Переопределения по умолчанию могут быть избыточными, но они здесь явно указаны, поскольку это пример.
Adisak
1
Я не мог понять, как это сделало бы все указатели нулевыми, не устанавливая их вручную, не могли бы вы объяснить, что вы здесь сделали?
Джонатан
1
@Jonathan: По сути, это «умный указатель», который ничего не делает, кроме установки указателя на ноль. IE вместо того Foo *a, что вы используете InitializedPointer<Foo> a- чисто академическое упражнение, так как Foo *a=0меньше набора текста. Однако приведенный выше код очень полезен с образовательной точки зрения. С небольшой модификацией (для "размещения" ctor / dtor и операций присваивания) его можно было легко расширить до различных типов интеллектуальных указателей, включая указатели с ограниченными областями (которые освобождаются от деструктора) и указатели со счетчиком ссылок, добавив inc / dec, когда m_pPointer установлен или очищен.
Adisak
2

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

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

int j;
char *foo;

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

PM100
источник
это ГАРАНТИРУЕТСЯ, или это обычная практика, используемая современными компиляторами?
gha.st
1
статические переменные инициализируются значением 0, что верно и для указателей (т.е. устанавливает для них значение NULL, а не все биты 0). Такое поведение гарантируется стандартом.
Алок Сингхал,
1
инициализация статических данных до нуля гарантируется стандартами C и C ++, это не просто обычная практика
groovingandi
1
возможно, потому что некоторые люди хотят убедиться, что их стек правильно выровнен, они заранее объявляют все переменные в верхней части функции? Может быть, они пишут на диалекте ac, который ТРЕБУЕТ этого?
KitsuneYMG
1

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

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

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

Надеюсь, это поможет и имеет смысл,

t0mm13b
источник
0

Что ж, если бы C ++ действительно инициализировал указатели, то у C ++, жалующегося на то, что «C ++ медленнее, чем C», было бы что-то реальное, за что можно было бы держаться;

Фред
источник
Это не моя причина. Моя причина в том, что если оборудование имеет 512 байт ПЗУ и 128 байт ОЗУ и дополнительную инструкцию обнуления, указатель будет даже одним байтом, что составляет довольно большой процент от всей программы. Мне нужен этот байт!
Джерри Иеремия,
0

C ++ происходит из фона C - и отсюда есть несколько причин:

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

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

gha.st
источник
Фактически, в C, если вы хотите выделить память с нулевыми значениями, вы можете использовать ее calloc().
Дэвид Торнли,
1
просто моя точка зрения - если вы хотите сделать это, вы можете, но это не делается для вас
автоматически
0

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

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

shoosh
источник
3
Начиная с C ++ 11, избегайте auto_ptrи заменяйте unique_ptr.
Дедупликатор
-2

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

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

Чарльз Эли сыр
источник
Если указатель не ожидается , будет NULL, инициализирует его , что это так же ошибка.
Дедупликатор