вівторок, 29 серпня 2017 р.

Прекратите проверять на NULL

Рано или поздно в программах на C/C++ приходится использовать передачу аргументов в функцию по указателю. Примером может служить хотя бы тот же const char* при использовании библиотек доступа к базам данных:

void Execute(const char* sql_statement);

Бывают и случаи передачи объектов и примитивных типов по указателю.

Проверка параметров на NULL

Мне хотелось бы рассказать про то, что наверняка делают многие программисты на C и C++: проверяют входной параметр-указатель на NULL (nullptr):

void Execute(const char* sql_statement) {
    if (sql_statement == nullptr) {
      throw std::invalid_argument("Null SQL Statement");
    }

Кроме варианта с исключением есть так же вариант с assert(sql_statement).

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

Причина здесь проста: указатель может содержать множество «невалидных» значений. Если вы считаете, что указатель невалиден только когда его значение NULL (nullptr), то вы ошибаетесь.

Ассемблер

Давайте взглянем на ассемблерный код следующего куска кода:

volatile int* p = nullptr;
// mov DWORD PTR ds:0, 0
*p = 0xDEADBEEF;
// ud2

Мой компилятор — gcc 4.9.0. Платформа — Intel x64.
В результате мы получаем инструкцию ud2 — Undefined Instruction, что логично — по стандарту разыменование нулевого указателя — неопределенное поведение.

Давайте взглянем на следующий код:

volatile int* p = reinterpret_cast<volatile int*> (13);
// mov DWORD PTR ds:13
*p = 0xDEADBEEF;
// -559038737

По адресу ds:13 пытаемся записать число -559038737. Скорее всего, если вы скомпилируете программу и запустите ее, вы получите Segmentation fault, так как по адресу 13 для вашей программы не выделено страниц памяти.

Таким образом почти любую функцию, которая проверяет валидность указателя можно обмануть, просто передав туда 13, хоть даже 4, 8, 15, 16, 23, 42, в общем, тот адрес, где для вас не выделено необходимых страниц памяти.

Так зачем стоит проверять на nullptr?

Проверять на NULL стоит в случае, если вы имеете опциональное значение, которое может быть, а может и не быть:

void Execute(const char* sql_statement,
             void (*callback)(State state)) {
    if (callback) {
      callback(OK);
    }
}

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

Немає коментарів:

Дописати коментар

HyperComments for Blogger

comments powered by HyperComments