В C или С++ следует проверить параметры указателя на NULL/nullptr?

Этот вопрос был вдохновлен этим ответом.

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

Все, что не определено, undefined.

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

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

Ответ 1

В общем, я не вижу значения при обнаружении NULL (почему NULL, а не какой-то другой недопустимый адрес?) для публичного API, я бы, вероятно, все еще делал это просто потому, что многие программисты на C и С++ ожидают такого поведения.

Ответ 2

Если вы собираетесь проверять аргументы указателя NULL, в которых вы не заключили контракт, чтобы принимать и интерпретировать их, сделайте это с помощью assert, а не возврата условной ошибки. Таким образом, ошибки в вызывающем абоненте будут незамедлительно обнаружены и могут быть исправлены, что позволяет легко отключить накладные расходы в производственных сборках. Я спрашиваю значение assert, за исключением документации; segfault от разыменования указателя NULL столь же эффективен для отладки.

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

Ответ 3

В С++, если вы не хотите принимать указатели NULL, тогда не используйте этот шанс: вместо этого примите ссылку.

Ответ 4

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

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

Ответ 5

На мой взгляд, это не вопрос ответственности. Это вопрос надежности.

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

Ответ 6

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

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

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

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

Ответ 7

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

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

Ответ 8

Ответ будет другим для C и С++.

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

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

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

Ответ 9

Я занимаюсь защитным программированием.

Если вы не можете профилировать, что эти проверки nullptr происходят в узком месте вашего приложения... (в таких случаях возможно, что в этих точках не нужно делать эти тесты значений указателей)

но в целом сравнение int с 0 действительно дешево. Я считаю, что стыдно позволять потенциальным ошибкам при сбоях, а не потреблять так мало CPU.

так: Проверьте свои указатели на NULL!

Ответ 10

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

Ответ 11

Для С++, если ваша функция не принимает значение nullpointer, используйте ссылочный аргумент. В общем.

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

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

Приветствия и hth.,

Ответ 12

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

Позорная ошибка glibc - это совсем другое. Неправильное использование привело к действительно полезному поведению для вызывающего абонента, и API оставался таким образом на протяжении десятилетий. Затем они изменили его.

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

Ответ 13

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

Ответ 14

Я не думаю, что ответственность за обращение с такими вещами несет

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

Ответ 15

По моему мнению, это ответственность за соблюдение его контракта.

Если вызываемый пользователь не должен принимать NULL, тогда он должен assert, что.

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

Как пользователь API, я хочу иметь возможность продолжить его использование без сбоя программы; Я хочу, чтобы хоть как-то восстановиться, или в худшем случае прекратить.

Ответ 16

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

Ответ 17

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

Я считаю совершенно бессмысленным, что функции, ожидающие ввода, должны проверять значение NULL. Или что-то другое в этом отношении. Единственной задачей функции является выполнение задачи на основе ее ввода или области видимости, ничего другого. Если у вас нет допустимого ввода или нет ввода вообще, тогда даже не вызывайте функцию. Кроме того, NULL-check не обнаруживает другие миллионы и миллионы возможных недопустимых значений. Вы знаете, что на forehand вы пропустили бы NULL, так почему бы вам все-таки передать его, потратить ценные циклы на еще один вызов функции с передачей параметра, сравнение функций с некоторыми функциями, а затем снова проверить выход функции для успеха или нет, Конечно, я мог бы это сделать, когда мне было 6 лет назад в 1982 году, но эти дни уже давно исчезли.

Существует, конечно, аргумент, который нужно сделать для публичных API. Как и некоторые DLL, предлагающие проверку идиотов. Вы знаете, эти аргументы: "Если пользователь поставляет NULL, вы не хотите, чтобы ваше приложение вышло из строя". Какой не аргумент. Это пользователь, который в первую очередь передает фиктивные данные; это явный выбор и не что иное. Если вы чувствуете, что это качество, хорошо... Я предпочитаю прочную логику и производительность над такими вещами. Кроме того, программист должен знать, что он делает. Если он работает с недействительными данными для конкретной области, то у него нет бизнеса, называющего себя программистом. Я не вижу причин понижать производительность, увеличивать потребление энергии, увеличивая размер двоичных файлов, что, в свою очередь, влияет на кэширование команд и предсказание ветвей моих продуктов, чтобы поддерживать таких пользователей.

Ответ 18

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

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

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

Ответ 19

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

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

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

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

Ответ 20

Накладные расходы на время разработки + производительность исполнения имеют компромисс с надежностью API, который вы разрабатываете.

Если API, который вы публикуете, должен запускаться внутри процесса вызывающей процедуры, вы НЕ ДОЛЖНЫ проверять наличие NULL или недопустимых аргументов. В этом случае, если вы потерпите крах, клиентская программа выйдет из строя, и разработчик, использующий ваш API, должен исправить свои пути.

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