typos and comments

This commit is contained in:
Dmis
2021-02-20 00:40:04 +03:00
parent ba765987e4
commit 5c76dcd7b9
+12 -8
View File
@@ -1,9 +1,9 @@
# Разыменование нулевых указателей.
Самая крутая ошибка с самыми жуткими последствиями. `null` вообще называют [ошибкой на миллиард долларов](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/).
От них страдает куча кода, на самых разных языках программирования. Но если в условной `Java` при обращении по `null`-ссылке вы получите исключение с вполне предсказуемыми последствиями (ну, упало и упало), то в великом и ужасном C++, а также в C за вами придет неопделенное поведение. И оно будет дествительно неопределенным!
От них страдает куча кода, на самых разных языках программирования. Но если в условной `Java` при обращении по `null`-ссылке вы получите исключение с вполне предсказуемыми последствиями (ну, упало и упало), то в великом и ужасном C++, а также в C за вами придет неопределенное поведение. И оно будет дествительно неопределенным!
Но для начала, конечно, надо отметить, что, после всех обсуждений туманных формулировок стандарта, в настоящее время есть некоторое [соглашение](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#315), что все-таки не сама по себе конструкция `*p`, где `p` — нулевой указатель, вызывает неопределенное поведение. А `lvalue-to-rvalue` преобразование. Ну или менее формально, кратко и не совсем правильно: пока нет чтения или записи значения, по этому самому нулевому адресу — все нормально.
Но для начала, конечно, надо отметить, что, после всех обсуждений туманных формулировок стандарта, в настоящее время есть некоторое [соглашение](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#315), что все-таки не сама по себе конструкция `*p`, где `p` — нулевой указатель, вызывает неопределенное поведение. А `lvalue-to-rvalue` преобразование. Ну или менее формально, кратко и не совсем правильно: пока нет чтения или записи значения по этому самому нулевому адресу — все нормально.
Так, сейчас совершенно законно вы можете вызвать статические методы класса через `nullptr`.
@@ -36,9 +36,10 @@ using val_t = decltype(LVALUE(S).foo());
```
Но, несмотря на то что так делать _можно_, совершенно не значит, что так делать _нужно_.
Потому что последствия от разыменования `nullptr` там, где этого делать нельзя, могут быть печальными. И совершенно не стоит ходить по краю такого лезвия.
Потому что последствия от разыменования `nullptr` там, где этого делать нельзя, могут быть печальными.
Лезвие тонкое, острое, можно легко оступиться и что-нибудь взорвать.
Если разыменовать `nullptr`, может быть вызван код, который никак вызываться [не должен был](https://godbolt.org/z/hPje47):
Если разыменовать `nullptr`, может быть исполнен код, который никак [не вызывался](https://godbolt.org/z/hPje47):
```C++
#include <cstdlib>
@@ -60,7 +61,7 @@ int main() {
}
```
Компилятор обнаруживат разыменование `nullptr` (вызов функции `Do`). Этого неопределенное поведение. Такого быть не может. Компилятор находит, что есть одно место, где этому указателю присваивается ненулевое значение. А значит именно это значение он и будет использовать. Как результат — вызывается функция, которую мы не вызывали.
Компилятор обнаруживает разыменование `nullptr` (вызов функции `Do`). Это неопределенное поведение. Такого быть не может. Компилятор находит, что есть одно место, где этому указателю присваивается ненулевое значение. И раз нуля быть не может, то, значит, именно это значение он и использует. Как результат — исполняется код функции, которую мы не вызывали.
Или вот совершенно дурная программа.
```C++
@@ -97,11 +98,12 @@ void run(int* ptr) {
```
Такая ситуация уже куда ближе к реальности.
В стандартной библиотеке C, например, есть функции, от которых можно было бы, по неопытности, ожидать проверки на `nullptr`, но они этого не делают.
`strlen`, `strcmp`, другие строковые функции, в C++ еще конструктор `std::string(const char*)` — их вызов с `nullptr` в качестве аргумента ведет к неопределенному поведению (и удалению нижерасположенных проверок, если вам не повезет).
`strlen`, `strcmp`, другие строковые функции, а в C++ еще конструктор `std::string(const char*)` — их вызов с `nullptr` в качестве аргумента ведет к неопределенному поведению (и удалению нижерасположенных проверок, если вам не повезет).
Еще есть особо мерзкие в этом смысле `memcpy` и `memmove`. Которые, несмотря на принимаемые в аргументах размеры буферов, все равно приводят к неопределенному поведению, если передать в них `nullptr`!
Еще есть особо мерзкие в этом смысле `memcpy` и `memmove`. Которые, несмотря на принимаемые в аргументах размеры буферов, все равно приводят к неопределенному поведению, если передать в них `nullptr` и нулевой размер!
И точно также это может проявиться в удалении ваших проверок.
```C++
@@ -115,7 +117,9 @@ int main(int argc, char **argv) {
}
char buffer[LENGTH];
memcpy(buffer, string, length);
memcpy(buffer, string, length); // при передаче nullptr
// length будет нулевым,
// но это не спасает от UB
buffer[length] = 0;
if (string == NULL) {