Когда проверять наличие nullptr внутри функций с параметром указателя? - PullRequest
0 голосов
/ 10 июля 2020

Исходя из C ++, где у нас есть const ссылки, я всегда пытаюсь решить эту проблему в C.

Если у меня есть что-то подобное в C:

struct Vector3 {
    float x,y,z;
};

void test(struct Vector3 *va, struct Vector3 *vb) {

    // Check for nullptr... 
    if (va == NULL || vb == NULL) {
       // log / exit program
    }
    // bla bla...
}

В C ++ я могу использовать ссылку const, чтобы не разрешать указатель NULL, но не в C. Когда вы проверяете наличие null в ваших функциях? Или какие правила вы используете, чтобы проверять или не проверять? Потому что кажется неправильным проверять каждую функцию, которая получает указатель, является ли указатель нулевым или нет.

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

Ответы [ 7 ]

4 голосов
/ 11 июля 2020

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

Вы можете позволить компилятору помочь вам обеспечить выполнение таких контрактов, объявив аргумент указателя, который не 'не принимает нулевые указатели как type arg[static 1] (диагностика c не требуется, но clang предупреждает, когда вы передаете NULL через такие аргументы), а не type *arg или с помощью нестандартного атрибута функции __attribute((nonnull(arg_index))) (приводит к Diagnosti c, когда вы передаете NULL через такие аргументы), но они не требуются для правильности.

assert s (которые опускаются в сборках выпуска) также могут быть полезны.

4 голосов
/ 11 июля 2020

Существует разница между ошибкой logi c и ошибкой утверждения. Если вашей функции разрешено запускаться только с допустимым указателем, то пользователь вашей функции несет ответственность и должен проверить, является ли это действительным указателем, прежде чем вводить вашу функцию. Стандартные функции C документируют такие состояния, как «неопределенное поведение», потому что буквально не определено, как strcpy(NULL, "abc") должен реагировать - передача таких значений функциям просто недопустима, и программист (другой код) несет ответственность за проверку таких ошибок.

Если так, то здесь assert() вступает в игру. assert предназначался для срабатывания только в отладочной конфигурации вашего проекта и должен быть удален (чтобы не снижалась производительность) в выпускной конфигурации вашего проекта - отсюда и макрос NDEBUG. Принципы НАСА критического для безопасности кода сообщает, что «плотность утверждений кода должна составлять минимум два утверждения на функцию».

Итак, если передача NULL вашей функции является «недопустимым состоянием», в этом случае поведение вашей функции «не определено», потому что просто не имеет смысла передавать NULL (или любой другой недопустимый указатель), я обычно пишу:

struct Vector3 {
    float x,y,z;
};

void test(struct Vector3 *va, struct Vector3 *vb) {
    assert(va != NULL);
    assert(vb != NULL);
    // bla bla...
}

(и на g cc Я бы добавил __attribute__((__nonnull__)), а с новейшим g cc с -std=c2x мы можем [[gnu::nonnull]]). Имейте в виду, что assert() расширяется до «ничего», когда NDEBUG определено (ie. Выражение не вычисляется), поэтому не помещайте операторы с побочными эффектами внутри assert().

3 голосов
/ 11 июля 2020

При записи в C я обычно использую подход C: документ требования к аргументам функции и предполагаю, что вызывающая сторона предоставляет аргументы, которые соответствуют документации. Таким образом, если я документирую, что аргумент указателя должен быть действительным указателем на struct Vector3 (например), я обычно не выполняю нулевую проверку, просто позволяя чипам упасть там, где они могут, если вызывающий не подчиняется.

В случаях, когда вы могли бы или могли использовать конкретно ссылку const в C ++, у вас также есть альтернатива передачи структур, объединений и скаляров (но не массивов) по значению. Для небольших структур, таких как те, что в вашем примере, в некоторых случаях это может даже привести к небольшому увеличению производительности. Пример:

void test(struct Vector3 va, struct Vector3 vb) {
    // bla bla...
}
1 голос
/ 11 июля 2020
• 1000 * - может иметь другое название в зависимости от конкретного c устройства). Это то, что нам обычно не нравится.

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

Подведем итоги основного сценария ios:

  1. Мы хотим проверить , когда наша функция является API, который мы доставляем третьим лицам. Кто-то должен выполнить проверку, и, поскольку мы не можем быть уверены, что заказчик напишет безопасный код, мы проверяем параметры и возвращаем ошибку в случае недопустимых значений. (В качестве исключения у нас может быть хорошо документированный API, в котором клиента предупреждают, что параметр NULL приведет к ошибке sh).
  2. Мы можем проверьте или нет , если наша функция является утилитой, которую мы используем в нашей собственной программе (например, встроенной прошивкой). Мы разработали наш код так, чтобы мы могли решить, что проверка достоверности параметров всегда отвечает за вызывающего (в данном случае мы не проверяем) или что он всегда отвечает за вызываемую функцию ( в данном случае мы не проверяем).
  3. Мы можем избежать проверки , если мы настолько уверены в своем дизайне, что уверены, что каждый вызов функции будет иметь допустимый параметр. Или, в качестве альтернативы, когда память fla sh нашего встроенного ПО настолько ограничена в пространстве, что нам нужно сохранить весь дополнительный код, полученный из этих if (ptr != NULL) проверок. В последнем случае возможен даже перезапуск системы.
0 голосов
/ 11 июля 2020

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

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

void func(mytype *ptr)
{
    if (!ptr) return;

    /* important stuff */
}

или

void func(mytype *ptr)
{
    if (ptr) {
        /* important stuff */
     }
}

Если это что-то вроде «необязательной» вещи, то вы можете проверить «точку использования», например:

void func(mytype *ptr)
{
    /* important stuff that's always done */

    /* Optionally, if the ptr is not null, set it */
    if (ptr) {
        *ptr = /* a value that indicates some status or a calculated result */;
    }
}
0 голосов
/ 11 июля 2020

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

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

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

0 голосов
/ 10 июля 2020

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...