mirror of
https://github.com/Nekrolm/ubbook.git
synced 2026-06-09 13:14:18 +03:00
typos and comments
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user