Проверка пустой указатель перед использованием указателя

Большинство людей использует указатели как это...

if ( p != NULL ) {
  DoWhateverWithP();
}

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

Мой вопрос, это могло возможно быть более выгодно для просто не, проверяют на ПУСТОЙ УКАЗАТЕЛЬ? Очевидно, на безопасности критические системы это не опция, но Ваша программа, отказывающая в пламени славы, более очевидна, чем функция не быть названным, если программа может все еще работать без него.

Относительно первого вопроса Вы всегда проверяете на ПУСТОЙ УКАЗАТЕЛЬ перед использованием указателей?

Во-вторых, полагайте, что у Вас есть функция, которая берет указатель в качестве аргумента, и Вы используете эту функцию многократно на нескольких указателях всюду по Вашей программе. Вы находите более выгодным протестировать на ПУСТОЙ УКАЗАТЕЛЬ в функции (преимущество, являющееся Вами, не должны тестировать на ПУСТОЙ УКАЗАТЕЛЬ повсеместно), или на указателе прежде, чем вызвать функцию (преимущество, являющееся никакими издержками от вызывания функции)?

11
задан trikker 17 December 2009 в 05:15
поделиться

13 ответов

Дон ' t возьмите за правило просто проверять наличие null и ничего не делать, если вы его обнаружите.

Если указателю разрешено иметь значение null, тогда вы должны подумать о том, что делает ваш код в случае, если он на самом деле равен нулю. Обычно просто ничего не делать - неправильный ответ. Можно с осторожностью определить API, которые работают подобным образом, но для этого требуется нечто большее, чем просто разбросать несколько проверок NULL по месту.

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

Если указатель не может быть нулевым, то вполне разумно написать код, который вызывает неопределенное поведение, если он равен нулю. Это ничем не отличается от написания подпрограмм обработки строк, которые вызывают неопределенное поведение, если ввод не завершается NUL, или написание подпрограмм с использованием буфера, которые вызывают неопределенное поведение, если вызывающий передает неправильное значение длины, или запись функции, которая принимает параметр файл * и вызывает неопределенное поведение, если пользователь передает файл дескриптор reinterpret_cast в файл * . В C и C ++ вам просто нужно полагаться на то, что вам говорит вызывающий абонент. Мусор на входе, мусор на выходе.

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

Используя аналогию из комментария Фрэнси по этому вопросу: большинство людей не ищут машины, переходя пешеходную дорожку или прежде чем сесть на диван. Их все еще может сбить машина. Такое случается. Но, как правило, считается параноиком потратить какие-либо усилия на проверку автомобилей в таких обстоятельствах или на то, чтобы в инструкции на банке с супом было сказано: «Сначала проверьте машины на своей кухне. Затем нагрейте суп».

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

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

[Edit - Я буквально только что наткнулся на пример ошибки, когда проверка на null не могла найти недопустимый указатель. Я собираюсь использовать карту для хранения некоторых объектов. Я буду использовать указатели на эти объекты (для представления графика), и это нормально, потому что карта никогда не перемещает свое содержимое. Но я еще не определил порядок объектов (и это будет немного сложно). Итак, чтобы сдвинуть дело с мертвой точки и доказать, что какой-то другой код работает, я использовал вектор и линейный поиск вместо карты. Правильно, я не имел в виду вектор, я имел в виду deque. Итак, после первого изменения размера вектора я не передавал нулевые указатели в функции, но я передавал указатели в память, которая была освобождена.

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

8
ответ дан 3 December 2019 в 01:00
поделиться

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

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

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

Мораль: проверьте NULL в вызываемой функции, либо с помощью некоторого выражения if (), которое выдает, либо с помощью какой-либо конструкции ASSERT (возможно, с четким сообщением о том, почему это произошло). Также проверьте наличие NULL в вызывающих объектах, но только если вызывающие знают , что это условие может произойти при нормальном выполнении программы, и действуют соответственно.

15
ответ дан 3 December 2019 в 01:00
поделиться

Я предпочитаю этот стиль:

if (p == NULL) {
    // throw some exception here
}

DoWhateverWithP();

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

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

8
ответ дан 3 December 2019 в 01:00
поделиться

Когда допустимо, чтобы программа просто аварийно завершила работу при появлении указателя NULL , я неравнодушен к:

assert(p);
DoWhateverWithP();

Это будет проверять указатель только в отладочных сборках с момента определения NDEBUG обычно #undef s assert () на уровне препроцессора. Он документирует ваше предположение и помогает с отладкой, но не оказывает никакого влияния на производительность выпущенного двоичного файла (хотя, честно говоря, проверка указателя NULL не должна иметь практически нулевого влияния на производительность в подавляющем большинстве случаев).

В качестве побочного преимущества это допустимо как для C, так и для C ++ и, в последнем случае, не требует включения исключений в вашем компиляторе / среде выполнения.

Что касается вашего второго вопроса, я предпочитаю задать утверждения в начале подпрограммы. Опять же, прелесть assert () заключается в том, что на самом деле нет никаких «накладных расходов», о которых можно было бы говорить. Таким образом, нет ничего, что могло бы сравниться с преимуществами требования только одного утверждения в определении подпрограммы.

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

assert(p = malloc(1)); // NEVER DO THIS!
DoSomethingWithP();    // If NDEBUG was defined, malloc() was never called!
10
ответ дан 3 December 2019 в 01:00
поделиться

Эта проверка ненулевого следует избегать , используя ссылки вместо указателей . Таким образом, компилятор гарантирует, что переданный параметр не равен NULL. Например:

void f(Param& param)
{
    // "param" is a pointer that is guaranteed not to be NULL      
}

В этом случае проверка должна выполняться клиентом. Однако в большинстве случаев ситуация с клиентом будет такой:

Param instance;
f(instance);

Никакой проверки ненулевого значения не требуется.

При использовании с объектами, размещенными в куче, вы можете сделать следующее:

Param& instance = *new Param();
f(*instance);

Обновить : Как пользователь Crashworks примечания, все еще возможно вызвать сбой программы. Однако при использовании ссылок ответственность за передачу действительной ссылки лежит на клиенте, и, как я показываю в примере, это очень легко сделать.

2
ответ дан 3 December 2019 в 01:00
поделиться

В дополнение к другим ответам это зависит от того, что означает NULL . Например, этот код совершенно нормален и довольно идиоматичен:

while (fgets(buf, sizeof buf, fp) != NULL) {
    process(buf);
}

Здесь значение NULL указывает не только на ошибку, но и на состояние конца файла. Аналогичным образом, strtok () возвращает NULL , чтобы сказать: «токенов больше нет» (хотя для начала следует избегать strtok () , но я отвлекся ). В таких случаях совершенно нормально вызывать функцию, если возвращаемый указатель не NULL , и ничего не делать в противном случае.

Изменить : другой пример, ближе на то, что был задан:

const char *data = "this;is;a;test;";
const char *curr = data;
const char *p;
while ((p = strchr(curr, ';')) != NULL) {
    /* process data in [curr, p) */
    process(curr, p);
    curr = p + 1;
}

Еще раз, NULL здесь - указание от strchr () , что он не смог найти ; , и что мы должны прекратить дальнейшую обработку данных.

Сказав, что, если NULL не используется в качестве индикатора, тогда это зависит:

  • Если указатель может ' t be NULL на этом этапе кода, полезно иметь assert (p! = NULL); при разработке, и также с fprintf (stderr, "Не может случиться \ n"); или эквивалентный оператор, а затем примите любое действие, которое необходимо ( abort () или подобное, вероятно, единственный разумный выбор на данный момент) .
  • Если указатель может иметь значение NULL и это не критично, может быть лучше просто обойти использование нулевого указателя. Предположим, вы пытаетесь выделить память для записи сообщения журнала, и malloc () не работает. Ты не должен t прервать выполнение программы из-за этого. Если malloc () завершается успешно, вы хотите вызвать функцию ( sprintf () / что угодно) для заполнения буфера.
  • Если указатель может быть NULL ], и это критично. В этом случае вы, вероятно, захотите потерпеть неудачу, и, надеюсь, такие условия случаются не слишком часто.

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

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

Стандартные библиотечные функции, по большей части, не имеют Например, не проверять функции NULL : str * , mem * . Исключением является free () , он выполняет проверку на NULL .

Комментарий к assert : assert не работает, если определено NDEBUG , поэтому не следует использовать его для отладки - он используется только во время разработки для выявления ошибок программирования. Кроме того, в C89 assert принимает int , поэтому assert (p! = NULL) лучше в таких случаях, чем простой assert ( p) .

4
ответ дан 3 December 2019 в 01:00
поделиться

Как насчет комментария, разъясняющего намерение? Если намерение - «этого не может произойти», тогда, возможно, правильнее было бы сделать assert вместо оператора if.

С другой стороны, если нулевое значение является нормальным, возможно, «комментарий else» было бы уместно объяснить, почему мы можем пропустить шаг «затем». У Стива МакКоннела в «Завершенном коде» есть хороший раздел об операторах if / else и о том, как отсутствие else является очень распространенной ошибкой (отвлекся, забыл?).

Следовательно, я обычно добавляю комментарий для «нет» -op else ", если это не что-то вроде" если сделано, вернуть / прервать ".

1
ответ дан 3 December 2019 в 01:00
поделиться

When you check for NULL, it is not good idea just to skip the function call. You should have an else-part that does something meaningful in case of NULL, for example throws an error or returns error code to upper level.

On the other hand, NULL is not always an error. It is often used to indicate for example that end of data has been reached. In such case, you will have to handle the situation as normal program flow.

1
ответ дан 3 December 2019 в 01:00
поделиться

Думаю, я видел больше. Таким образом, вы не будете действовать, если знаете, что он все равно взорвется.

if (NULL == p)
{
  goto FunctionExit; // or some other common label to exit the function.
}
0
ответ дан 3 December 2019 в 01:00
поделиться

Итак, ответ на первый вопрос: вы говорите об идеальной ситуации, большая часть кода, который я вижу, который использует if (p! = NULL) , является устаревшим. Также предположим, что вы хотите вернуть оценщик, а затем вызвать оценщик с данными, но скажите, что для этих данных нет оценщика, имеет логический смысл вернуть NULL и проверить на NULL перед вызовом оценщика.

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

ABC(p);
a = DEF(p);
d = GHI(a);
JKL(p, d);

, но этот код будет намного лучше:

if(p)
{
 ABC(p);
 a = DEF(p);
 d = GHI(a);
 JKL(p, d);
}
0
ответ дан 3 December 2019 в 01:00
поделиться

Я думаю, что лучше проверить значение null. Хотя, вы можете сократить количество проверок, которые вам нужно сделать.

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

if (p == NULL) return;

Тем не менее, я обычно проверяю только те функции, которые являются общедоступными.

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

Инициализация конструктора может использоваться как альтернатива постоянной проверке на null. Это особенно полезно, когда класс содержит коллекцию. Коллекцию можно использовать во всем классе, не проверяя, инициализирована ли она.

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

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

Инициализация конструктора может использоваться как альтернатива постоянной проверке на null. Это особенно полезно, когда класс содержит коллекцию. Коллекцию можно использовать во всем классе, не проверяя, инициализирована ли она.

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

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

Инициализация конструктора может использоваться как альтернатива постоянной проверке на null. Это особенно полезно, когда класс содержит коллекцию. Коллекцию можно использовать во всем классе, не проверяя, инициализирована ли она.

)

Инициализация конструктора может использоваться как альтернатива постоянной проверке на null. Это особенно полезно, когда класс содержит коллекцию. Коллекцию можно использовать во всем классе, не проверяя, инициализирована ли она.

)

Инициализация конструктора может использоваться как альтернатива постоянной проверке на null. Это особенно полезно, когда класс содержит коллекцию. Коллекцию можно использовать во всем классе, не проверяя, инициализирована ли она.

0
ответ дан 3 December 2019 в 01:00
поделиться

Может ли это Может быть, более выгодно просто не проверять NULL?

Я бы не стал этого делать, я предпочитаю утверждения на передовой и некоторую форму восстановления тела после этого. Что бы вам предоставили утверждения , а не , которые не выполняла бы проверка на null? Аналогичный эффект, с более простой интерпретацией и формальным подтверждением.

Что касается первого вопроса, всегда ли вы проверяете NULL перед использованием указателей?

Это действительно зависит от кода и доступного времени, но меня раздражает хорош в этом; хороший кусок "реализации" в моих программах состоит из того, что программа должна не делать, а не из обычного «что она должна делать».

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

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

0
ответ дан 3 December 2019 в 01:00
поделиться

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

Поскольку поведение разыменования нулевого указателя не определено, он может делать все . Крушение, испорченная память, создание червоточины в альтернативном измерении, позволяющей Старшим Богам явиться и поглотить все человечество ... что угодно. Ошибки случаются, но неопределенное поведение по определению является ошибкой. Так что не делайте этого намеренно .

0
ответ дан 3 December 2019 в 01:00
поделиться
Другие вопросы по тегам:

Похожие вопросы: