Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
книги хакеры / Питер_Гудлиф_Ремесло_программиста_Практика_написания_хорошего_кода.pdf
Скачиваний:
16
Добавлен:
19.04.2024
Размер:
9.23 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Природаm

этого зверя

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

217Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Элементарные семантические ошибки

Большинство сбоев времени исполнения обусловлено очень просты% ми ошибками, вызывающими некорректное поведение. Классиче% ским примером служит использование неинициализированных пе% ременных, которое бывает трудно выявить: поведение программы оказывается зависящим от случайного значения, оказавшегося в па% мяти, отведенной для переменной. Иногда программа работает пре% красно, а иногда дает сбой. Вот другие элементарные семантиче% ские ошибки:

Сравнение значений переменных с плавающей запятой на (не)равенство1

Выполнение вычислений без учета возможного числового пере% полнения

Округление ошибок при неявном преобразовании типов (часто теряется знак char)

Объявление переменной unsigned int foo, которая затем исполь% зуется в коде if (foo < 0) – приехали!

Такого рода семантические ошибки часто обнаруживаются средст% вами статического анализа.

Семантические ошибки

Это коварные ошибки, которые не ловятся средствами контроля, их гораздо труднее выявить. Семантическая ошибка может быть низко% уровневой, например использование не той переменной, отсутствие проверки допустимости входных параметров или неверно составлен% ный цикл. Уровень безголовости может оказаться выше: некоррект% ный вызов API или нарушение внутренней целостности объекта. В эту категорию попадают многие ошибки, связанные с памятью; их бывает чертовски трудно обнаружить благодаря способности изме% нять и калечить выполняющийся код, из%за чего он ведет себя совер% шенно непредсказуемым и необъяснимым образом.

Программы часто ведут себя странно. Утешает одно: они делают ровно то, что мы им приказали.

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

Взгляд из глубины

Разложив все ошибки по полочкам, рассмотрим их вблизи и обсудим некоторые из распространенных типов семантических ошибок:

1Этого делать нельзя: арифметика с плавающей точкой слишком приблизи%

тельна, чтобы точное сравнение имело какой%то смысл.

 

 

 

 

hang

e

 

 

 

 

 

 

C

 

E

 

 

 

X

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

t

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

w Click

 

 

 

218m

 

 

 

 

w

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

.

 

 

 

 

 

.c

 

 

p

 

 

 

 

g

 

 

 

 

df

 

 

n

e

 

 

 

 

-xcha

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

Глава 9. Поиск ошибокClick

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Ошибки сегментации

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

Такие ошибки легко возникают при опечатках во вводе кода с ука% зателями или при некорректных операциях с указателями. Типич% ная опечатка в C, приводящая к ошибке сегментации, – scanf("%d", number);. Отсутствие & перед number приводит к тому, что scanf пыта% ется писать по адресу памяти, соответствующему случайному зна% чению числа, в результате чего программа исчезает, оставив после себя облачко дыма. Но еще больший конфуз происходит, если зна% чение числа соответствует допустимому адресу памяти. Тогда код продолжит работу как ни в чем ни бывало, пока не потребуется па% мять, в которую вы только что произвели запись, и тогда ваша судь% ба непредсказуема.

Выход за границы памяти

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

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

Утечка памяти

Это постоянная угроза для языков, в которых нет сборки мусора.1 Когда вам требуется память, вы должны вежливо попросить ее у системы (с помощью malloc в C или new в C++). Потом, когда она больше не нужна, будьте любезны вернуть ее (с помощью free и de lete соответственно). Если вы поступите неприлично, забыв освобо% дить память, программа станет постепенно захватывать все больше

1Наличие сборки мусора тоже не гарантирует отсутствие утечки памяти. Соз% дайте ссылки двух объектов друг на друга, а потом обе удалите. Сборщик му%

сора должен быть довольно изощренным, иначе они останутся навечно.

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

 

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

Природаm

этого зверя

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

219Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

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

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

Исчерпание памяти

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

Есть операционные системы, которые не возвращают ошибку при неудачном запросе памяти – всякое выделение возвращает указа% тель на зарезервированную, но не выделенную страницу памяти. Когда в дальнейшем программа пытается получить доступ к этой странице, ОС перехватывает это обращение и действительно выде% ляет странице память, благодаря чему программа успешно продол% жает работу. Все идет хорошо, пока не кончится доступная память. Тогда ваша программа получит сигнал ошибки – спустя много време% ни после того, как состоялось соответствующее выделение памяти.1

Математические ошибки

Они встречаются в разном виде: исключения операций с плаваю% щей точкой, некорректные математические конструкции, перепол% нение/потеря значимости или выражения, дающие сбой (напри% мер, при делении на нуль). Даже при попытке вывести число с пла% вающей точкой, но фактически передав целое в printf("%f"), можно получить в программе математическую ошибку.

Зависание программы

Обычно происходит вследствие ошибочной логики программы: ча% ще всего причиной оказываются бесконечные циклы с неверно за% данными условиями завершения. Кроме того, в многопоточных приложениях встречаются взаимная блокировка и ситуации гонки,

1Это как раз характерно для Linux, по крайней мере, пока не закончится ад% ресное пространство виртуальной памяти. В таком случае malloc может воз%

вратить 0, но система внезапно рухнет.