Основы программирования в Linux
В тех случаях, когда внутренняя логика системы нуждается в подкреплении, X/Open предоставляет макрос
, применяемый для проверки правильности исходных данных и остановки выполнения программы в противном случае.assert<b>#include <assert.h></b><b>void assert(int expression)</b>Макрос
вычисляет выражение и, если оно не равно нулю, выводит некоторую диагностическую информацию о стандартной ошибке и вызывает функциюassertдля завершения программы.abortЗаголовочный файл assert.h определяет макросы в зависимости от определения флага
. ЕслиNDEBUGопределен во время обработки заголовочного файла,NDEBUGопределяется по существу как ничто. Это означает, что вы можете отключить проверки заданных выражений во время компиляции, компилируя с опциейassertили вставив перед включением файла assert.h строку-DNDEBUG#define NDEBUGв каждый исходный файл.
Этот метод применения порождает проблему. Если вы используете
во время тестирования, но отключите макрос в рабочем коде, в вашем рабочем коде может оказаться менее строгая проверка, чем применявшаяся в процессе его тестирования. Обычно макросыassertне оставляют включенными в рабочем коде — вряд ли вам понравится рабочий код, предоставляющий пользователю недружелюбное сообщениеassertи останавливающий программу. Быть может, лучше написать свою отслеживающую ошибки подпрограмму, которая проверяет выражение, использовавшееся в макросе, но не нуждается в полном отключении в рабочем коде.assert failedВы также должны убедиться в том, что у выражения макроса
нет побочных эффектов. Например, если вы применяете вызов функции с побочным эффектом, этот побочный эффект не проявится в рабочем коде с отключенными макросамиassert.assertВыполните упражнение 10.2.
Упражнение 10.2. Программа assert.c.Далее приведена программа assert.c, определяющая функцию, которая должна принимать положительное значение. Она защищает от ввода некорректного аргумента благодаря применению макроса
.assertПосле включения заголовочного файла assert.h и функции "квадратный корень", проверяющей положительное значение параметра, вы можете писать функцию
.main#include <stdio.h>#include <math.h>#include <assert.h>#include <stdlib.h>double my_sqrt(double x) {assert(x >= 0.0);return sqrt(x);}int main() {printf("sqrt +2 = %g\n", my_sqrt(2.0));printf("sqrt -2 = %g\n", my_sqrt(-2.0));exit(0);}Теперь при выполнении программы вы увидите нарушение в макросе
при передаче некорректного значения. Точный формат сообщения о нарушении условия макроса assert в разных системах разный.assert$ <b>сс -о assert assert.с -lm</b>$ <b>./assert</b>sqrt +2 = 1.41421assert: assert.c:7: my_sqrt: Assertion 'x >= 0.0' failed.Aborted$Как это работает
Когда вы попытаетесь вызвать функцию
с отрицательным числом, макросmy_sqrtдаст сбой. Он предоставляет файл и номер строки, в которой нарушено условие и само нарушенное условие. Программа завершается прерываниемassert. Это результат вызоваabortмакросомabort.assertЕсли вы перекомпилируете программу с опцией
, макрос-DNDEBUGне компилируется, и вы получаетеassert(Not a Number, не число) — значение, указывающее на неверный результат при вызове функцииNaNиз функцииsqrt.my_sqrt$ <b>cc -о assert -DNDEBUG assert.с -lm</b>$ <b>./assert</b>sqrt +2 = 1.41421sqrt -2 = nan$Некоторые более старые версии математической библиотеки генерируют исключение для математической ошибки, и ваша программа будет остановлена с сообщением "Floating point exception" ("Исключение для числа с плавающей точкой") вместо возврата NaN.
Устранение ошибок использования памяти
Распределение динамической памяти — богатый источник ошибок, которые трудно выявить. Если вы пишете программу, применяющую функции
иmallocдля распределения памяти, важно внимательно следить за блоками, которые вы выделяете, и быть уверенным в том, что не используется блок, который вы уже освободили.freeОбычно блоки памяти выделяются функцией
и присваиваются переменным-указателям. Если переменная-указатель изменяется, и нет других указателей, указывающих на блок памяти, он становится недоступным. Это утечка памяти, вызывающая увеличение размера программы. Если вы потеряете большой объем памяти, скорость работы вашей системы, в конце концов, снизится, и система уйдет за пределы памяти.mallocЕсли вы записываете в область, расположенную после конца выделенного блока (или перед началом блока), вы с большой долей вероятности повредите структуры данных, используемые библиотекой malloc, следящей за распределением памяти. В этом случае в какой-то момент времени вызов
или дажеmallocприведет к нарушению сегментации, и ваша программа завершится аварийно. Определение точного места возникновения сбоя может оказаться очень трудной задачей, поскольку нарушение могло возникнуть задолго до события, вызвавшего аварийное завершение программы.freeНеудивительно, что существуют коммерческие и бесплатные средства, способные помочь в решении проблем этих двух типов. Например, есть много разных версий функций
иmalloc, которые содержат дополнительный код для проверки выделения и освобождения блоков памяти и пытаются учесть двойное освобождение блока и другие типы неправильного использования памяти.free