Проверка на нулевой указатель в C / C ++ [закрыто]

153

В недавнем обзоре кода участник пытается обеспечить выполнение всех NULLпроверок указателей следующим образом:

int * some_ptr;
// ...
if (some_ptr == NULL)
{
    // Handle null-pointer error
}
else
{
    // Proceed
}

вместо того

int * some_ptr;
// ...
if (some_ptr)
{
    // Proceed
}
else
{
    // Handle null-pointer error
}

Я согласен, что его способ немного более понятен в том смысле, что он явно говорит: «Убедитесь, что этот указатель не равен NULL», но я бы возразил, сказав, что любой, кто работает над этим кодом, поймет, что использование переменной указателя в ifЗаявление неявно проверяется на NULL. Также я чувствую, что у второго метода меньше шансов внести ошибку типа:

if (some_ptr = NULL)

это просто абсолютная боль, чтобы найти и отладить.

Какой способ вы предпочитаете и почему?

Брайан Мрамор
источник
32
Наличие последовательного стиля иногда важнее, чем стиль, который вы выбираете.
Марк Рэнсом
15
@Mark: последовательность всегда является наиболее важным фактором вашего стиля.
sbi
13
«Также я чувствую, что у второго метода меньше шансов на введение ошибки типа« похожие »: if (some_ptr = NULL)». Вот почему некоторые люди используют if (NULL == some_ptr), не могут присвоить NULL, поэтому вы получите ошибка компиляции.
user122302
14
большинство компиляторов в наши дни предупреждают о назначении в условных выражениях - к сожалению, слишком много разработчиков игнорируют предупреждения компилятора.
Майк Эллери
10
Я не согласен с утверждением выше, что важна последовательность. Это не имеет никакого отношения к последовательности; в любом случае это не очень хорошо. Это та область, где мы должны позволить программистам выражать свой собственный стиль. Если есть человек, который не сразу понимает какую-либо форму, он должен немедленно прекратить чтение кода и подумать о смене карьеры. Я скажу больше: если есть косметическая консистенция, которую вы не можете автоматически применить с помощью сценария, удалите ее. Стоимость осушения человеческой морали является WAY важнее тех глупых решений , которые люди делают на встрече.
wilhelmtell

Ответы:

203

По моему опыту, тесты в форме if (ptr)или if (!ptr)являются предпочтительными. Они не зависят от определения символа NULL. Они не раскрывают возможность случайного назначения. И они понятны и лаконичны.

Редактировать: Как указывает SoapBox в комментарии, они совместимы с классами C ++, такими как auto_ptrобъекты, которые действуют как указатели и которые обеспечивают преобразование boolдля включения именно этой идиомы. Для этих объектов явное сравнение NULLдолжно вызывать преобразование в указатель, которое может иметь другие семантические побочные эффекты или быть более дорогим, чем простая проверка существования, которую boolподразумевает преобразование.

У меня есть предпочтение к коду, который говорит, что это означает, без лишнего текста. if (ptr != NULL)имеет то же значение, что и if (ptr)за счет избыточной специфичности. Следующая логическая вещь - это писать, if ((ptr != NULL) == TRUE)и в этом заключается безумие. Язык С ясно , что логическое значением проходит проверку if, whileили тому подобные имеет конкретный смысл ненулевого значения истинно и нуль является ложным. Избыточность не делает это понятнее.

RBerteig
источник
23
Кроме того, они совместимы с классами-оболочками указателей (shared_ptr, auto_ptr, scoped_tr и т. Д.), Которые обычно переопределяют оператор bool (или safe_bool).
SoapBox
2
Я голосую за этот ответ просто потому, что ссылка на auto_ptr добавлена ​​в обсуждение. После написания вопроса становится ясно, что это на самом деле более субъективно, чем что-либо еще, и, вероятно, не подходит для «ответа» стекопотока, поскольку любой из них может быть «правильным» в зависимости от обстоятельств.
Брайан Мрамор
2
@ Брайан, я уважаю это, и если придет «лучший» ответ, пожалуйста, не стесняйтесь перемещать галочку по мере необходимости. Что касается субъективности, то да, это субъективно. Но это, по крайней мере, субъективная дискуссия, в которой есть объективные проблемы, которые не всегда очевидны при постановке вопроса. Линия размыта. И субъективно ... ;-)
RBerteig
1
Одна важная вещь, которую стоит отметить: до недавнего времени я высказывался за то, чтобы писать «if (ptr)» вместо «if (ptr! = NULL)» или «if (ptr! = Nullptr)», так как это более сжато, поэтому код более читабелен. Но я только что обнаружил, что сравнение вызывает (соответственно) предупреждение / ошибку для сравнения с NULL / nullptr на последних компиляторах. Поэтому, если вы хотите проверить указатель, лучше сделать «if (ptr! = NULL)» вместо «if (ptr)». Если вы по ошибке объявите ptr как int, первая проверка вызовет предупреждение / ошибку, а последняя скомпилируется без предупреждения.
Арно
1
@ Дэвид Вонг Вонг Нет, это не то, что имелось в виду. Примером на этой странице является последовательность из двух строк, в int y = *x; if (!x) {...}которой оптимизатору разрешено рассуждать о том, что, поскольку *xон будет неопределенным, если он xравен NULL, он не должен быть NULL и, следовательно, !xдолжен быть FALSE. Выражение не!ptr является неопределенным поведением. В этом примере, если тест был выполнен перед любым использованием , тогда оптимизатор ошибочно удалит тест. *x
RBerteig
52

if (foo)достаточно ясно Используй это.

wilx
источник
25

Я начну с этого: согласованность важна, решение менее важно, чем согласованность в вашей кодовой базе.

В C ++

NULL определяется как 0 или 0L в C ++.

Если вы читали язык программирования C ++, Бьярн Страуструп предлагает 0явно использовать NULLмакрос, чтобы избежать макроса при выполнении присваивания , я не уверен, что он делал то же самое со сравнениями, я давно читал книгу, думаю, он просто сделал if(some_ptr)без явного сравнения, но я не уверен в этом.

Причина этого в том, что NULLмакрос обманчив (как почти все макросы), на самом деле он 0буквальный, а не уникальный тип, как следует из названия. Избегание макросов является одним из общих указаний в C ++. С другой стороны, 0выглядит как целое число, а не по сравнению с или присвоено указателям. Лично я мог бы пойти любым путем, но обычно я пропускаю явное сравнение (хотя некоторым людям это не нравится, поэтому, вероятно, у вас есть участник, предлагающий изменение в любом случае).

Независимо от личных чувств, это во многом выбор наименьшего зла, поскольку не существует единственного правильного метода.

Это ясная и распространенная идиома, и я предпочитаю, что нет случайного присваивания значения во время сравнения, и он ясно показывает:

if(some_ptr){;}

Это понятно, если вы знаете, что some_ptrэто тип указателя, но это также может выглядеть как целочисленное сравнение:

if(some_ptr != 0){;}

Это понятно, в общих случаях это имеет смысл ... Но это утечка абстракции, NULLна самом деле 0буквальная и может в итоге легко использоваться неправильно:

if(some_ptr != NULL){;}

C ++ 0x имеет nullptr, который теперь является предпочтительным методом, поскольку он явный и точный, просто будьте осторожны с случайным присваиванием:

if(some_ptr != nullptr){;}

До тех пор, пока вы не сможете перейти на C ++ 0x, я буду утверждать, что бесполезно беспокоиться о том, какие из этих методов вы используете, их все недостаточно, поэтому был изобретен nullptr (наряду с общими проблемами программирования, которые привели к идеальной пересылке). .) Самое главное, чтобы сохранить последовательность.

В С

С другой зверь.

В C NULL может быть определено как 0 или как ((void *) 0), C99 допускает реализацию определенных констант нулевого указателя. Так что на самом деле все сводится к определению реализации NULL, и вам придется проверить его в стандартной библиотеке.

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

С этой точки зрения, вероятно, я бы рекомендовал использовать NULLопределение макроса в C.

M2tM
источник
Т.Л., д - р , и , хотя вы правы 0в C ++, это имеет смысл в C: (void *)0. 0предпочтительнее, чем NULLв C ++, потому что ошибки типа могут быть PITA.
Мэтт Джоунер
@Matt: Но NULLвсе равно равно нулю.
GManNickG
@GMan: Не в моей системе: #define NULL ((void *)0)сlinux/stddef.h
Мэтт Джойнер
@Matt: в C ++. Вы сказали, что 0 предпочтительнее, но NULLдолжно быть ноль.
GManNickG
Это хороший момент, я не упомянул об этом, и я улучшаю свой ответ, предлагая его. ANSI C может иметь значение NULL, определенное как ((void *) 0), C ++ определяет значение NULL как 0. Я не обращался непосредственно к стандарту для этого, но я понимаю, что в C ++ NULL может быть 0 или 0L.
M2tM
19

Я пользуюсь if (ptr), но об этом совершенно не стоит спорить.

Мне нравится мой путь, потому что он лаконичен, хотя другие говорят, == NULLчто его легче читать и более наглядно. Я вижу, откуда они берутся, я просто не согласен с тем, что дополнительные вещи делают это проще. (Я ненавижу макрос, поэтому я пристрастен.) До вас.

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

Заметьте, в C ++ 0x мы можем сделать то if (ptr == nullptr), что мне кажется лучше. (Опять же, я ненавижу макрос. Но nullptrэто хорошо.) Но я все еще делаю if (ptr), только потому, что это то, к чему я привык.

GManNickG
источник
надеюсь, что дополнительные 5 лет опыта изменили ваше мнение :) если бы в C ++ / C был оператор, похожий на if_exist (), то это имело бы смысл.
Магулла
10

Честно говоря, я не понимаю, почему это важно. Любой из них достаточно ясен, и любой, кто имеет умеренный опыт работы с C или C ++, должен понимать и то, и другое. Один комментарий, хотя:

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

int f(void* p)
{
    if (!p) { return -1; }

    // p is not null
    return 0;
}

Таким образом, вы избегаете «кода стрелки».

Джеймс МакНеллис
источник
кто-то указал здесь kukuruku.co/hub/programming/i-do-not-know-c, что if(!p)является неопределенным поведением. Это может сработать или нет.
Дэвид 天宇 Вонг
@David 天宇 Вонг, если вы говорите о примере №2 по этой ссылке, if(!p)это не неопределенное поведение, это строка перед ним. И if(p==NULL)будет иметь точно такую ​​же проблему.
Марк Рэнсом
8

Лично я всегда использовал if (ptr == NULL)это, потому что это ясно показывает мои намерения, но на данный момент это просто привычка.

Использование =вместо ==будет поймано любым компетентным компилятором с правильными настройками предупреждений.

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

Марк Рэнсом
источник
7

Еще один момент в пользу foo == NULLпрактики: если foo, скажем, a int *или a bool *, то if (foo)проверка может быть случайно интерпретирована читателем как проверка значения pointee, то есть as if (*foo). NULLСравнение здесь является напоминанием о том, что мы говорим о указателе.

Но я полагаю, что хорошее соглашение об именах делает этот аргумент спорным.

Даниил Гершкович
источник
4

На самом деле, я использую оба варианта.

Есть ситуации, когда вы сначала проверяете правильность указателя, и если он равен NULL, вы просто возвращаете / выходите из функции. (Я знаю, что это может привести к обсуждению «должна ли функция иметь только одну точку выхода»)

Большую часть времени вы проверяете указатель, затем делаете то, что хотите, и затем устраняете ошибку. Результатом может быть уродливый код с отступом в x раз с несколькими if.

DWO
источник
1
Я лично ненавижу код стрелки, который может привести к использованию одной точки выхода. Почему бы не выйти, если я уже знаю ответ?
Ruslik
1
@ruslik: в C (или в стиле C-with-классы C ++) множественные возвраты затрудняют очистку. В «реальном» C ++ это, очевидно, не проблема, поскольку ваша очистка обрабатывается RAII, но некоторые программисты (по более или менее обоснованным причинам) застряли в прошлом и либо отказываются, либо не могут полагаться на RAII ,
Джалф
@jalf в таких случаях gotoможет быть очень удобным. Кроме того, во многих случаях malloc()очистку можно заменить более быстрой alloca(). Я знаю, что они не рекомендуются, но они существуют по определенной причине (по моему мнению, хорошо названный ярлык для gotoнамного чище, чем запутанное if/elseдерево).
Ruslik
1
allocaявляется нестандартным и совершенно небезопасным. Если это не удается, ваша программа просто взрывается. Шансов на восстановление нет, а поскольку стек относительно небольшой, вероятен сбой. В случае неудачи возможно, что вы закроете кучу и введете уязвимости, компрометирующие привилегии. Никогда не используйте allocaили vlas, если у вас нет крошечной границы размера, который будет выделен (и тогда вы могли бы просто использовать обычный массив).
R .. GitHub ОСТАНОВИТЬ ЛЬДА
4

Язык программирования C (K & R) попросит вас проверить на null == ptr, чтобы избежать случайного назначения.

Дерек
источник
23
K & R также спросит "что такое умный указатель?" В мире C ++ все меняется.
Алекс Емельянов
2
или, скорее, все изменилось уже в мире C ++ ";)
jalf
3
Где K & R заявляет, что (ссылка)? Они никогда не используют этот стиль сами. В главе 2 K & R2 они даже рекомендуют if (!valid)выше if (valid == 0).
Скот
6
Успокойся, ребята. Он сказал K & R, а не K & R. Они явно менее известны, поскольку их инициалы даже не пишутся с заглавной буквы.
jmucchiello
1
капитализация только замедляет вас
Дерек
3

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

И не то, чтобы это имело значение, а мое личное предпочтение if (ptr). Значение более очевидно для меня, чем даже if (ptr == NULL).

Может быть, он пытается сказать, что лучше разбираться с ошибочными условиями до счастливого пути? В этом случае я все еще не согласен с рецензентом. Я не знаю, что для этого существует общепринятая конвенция, но, на мой взгляд, наиболее «нормальное» условие должно стоять на первом месте в любом операторе if. Таким образом, мне нужно меньше копаться, чтобы выяснить, о чем эта функция и как она работает.

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

if (error_condition)
  bail_or_fix();
  return if not fixed;

// If I'm still here, I'm on the happy path

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

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

Дэррил
источник
1

Это одна из основ обоих языков, которую указатели оценивают по типу и значению, которые можно использовать в качестве контрольного выражения boolв C ++ и intв C. Просто используйте его.

Йенс Гастедт
источник
1
  • Указатели не являются логическими значениями
  • Современные компиляторы C / C ++ выдают предупреждение, когда вы пишете if (foo = bar)случайно.

Поэтому я предпочитаю

if (foo == NULL)
{
    // null case
}
else
{
    // non null case
}

или

if (foo != NULL)
{
    // non null case
}
else
{
    // null case
}

Однако, если бы я писал набор руководств по стилю, я бы не включал в него такие вещи, я бы писал такие:

Убедитесь, что вы делаете нулевую проверку указателя.

JeremyP
источник
2
Это правда, что указатели не являются логическими значениями, но в C операторы if не принимают логические значения: они принимают целочисленные выражения.
Кен,
@Ken: это потому, что С не работает в этом отношении. Концептуально это булево выражение и (на мой взгляд) должно рассматриваться как таковое.
JeremyP
1
В некоторых языках есть операторы if, которые проверяют только на null / not-null. В некоторых языках есть операторы if, которые проверяют только целое число на знак (трехсторонний тест). Я не вижу причин считать C "сломанным", потому что они выбрали другую концепцию, которая вам нравится. Есть много вещей, которые я ненавижу в C, но это просто означает, что программная модель C не совпадает с моей ментальной моделью, не то, что кто-либо из нас (я или C) сломлен.
Кен
@Ken: логическое значение не является числом или указателем концептуально , не важно , на каком языке.
JeremyP
1
Я не говорил, что логическое значение - это число или указатель. Я сказал, что нет оснований настаивать на том, что оператор if должен или мог бы принимать только логическое выражение, и предложил контрпримеры в дополнение к имеющемуся. Многие языки используют нечто иное, чем логическое, и не только в «нулевом / ненулевом» ключе Си. В вычислительной технике наличие оператора if, который принимает (только) логическое значение, является относительно недавней разработкой.
Кен
1

Я большой поклонник того , что C / C ++ не проверяет типы в логических условиях if, forи whileзаявлении. Я всегда использую следующее:

if (ptr)

if (!ptr)

даже на целые числа или другой тип, который преобразуется в bool:

while(i--)
{
    // Something to do i times
}

while(cin >> a >> b)
{
    // Do something while you've input
}

Кодирование в этом стиле более читабельно и понятно для меня. Просто мое личное мнение.

Недавно, работая над микроконтроллером OKI 431, я заметил следующее:

unsigned char chx;

if (chx) // ...

более эффективен, чем

if (chx == 1) // ...

потому что в более позднем случае компилятор должен сравнить значение chx с 1. Где chx - просто флаг true / false.

Donotalo
источник
1

Большинство компиляторов, которые я использовал, будут по крайней мере предупреждать о ifназначении без дополнительного синтаксического сахара, поэтому я не покупаю этот аргумент. Тем не менее, я использовал оба профессионально и не имею ни одного предпочтения. Это == NULLопределенно понятнее, хотя, на мой взгляд.

Майкл Дорган
источник