В процессе выполнении каких-либо вычислений могут возникать ошибки. Если ошибка произошла в main (условно), то можно просто вывести сообщение об ошибке. Если же ошибка произошла в функции, которая вызывается в разных частях программы, то желательно, чтобы функция могла каким-либо образом сообщить о возникшей ошибке, но при этом не выводила бы никаких сообщений. Решение о том, какое действие является предпочтительным при возникновении ошибки, принимает вызывающая сторона.
Например, если программа пытается открыть файл, то в одном случае отсутствие файла может быть критической ошибкой, а в другом — нет. Если бы функция fopen всегда выдавала сообщения об ошибке, то это привело бы к большому число "ложных ошибок".
Возможные методы информирования об ошибке включают:
Многие функции стандартной библиотеки Си возвращают выделенное значение и вместе с этим устанавливают значение глобальной переменной errno. Например, функция fopen возвращает нулевой указатель, если запрашиваемый файл не удалось открыть. Возвращаемое значение говорит о том, что операция не была выполнена, а значение глобальной переменной позволяет получить код ошибки.
В некоторых случаях бывает сложно выбрать специальное значение, которое будет сигнализировать об ошибке. Например, функция интегрирования должна вернуть значение с плавающей точкой, и любое значение является возможным. В этом случае можно передавать в функцию дополнительный аргумент. Это удобнее, чем возвращать структуру с несколькими полями. Рассмотрим следующий пример.
typedef enum { INT_OK=0, INT_LIMITS, INT_CONVERGENCE } ErrorCode;
typedef double (*RealFun)(double);
double integrate(RealFun f, double a, double b, double eps, ErrorCode *perr);
В заголовочном файле мы определяем возможные коды ошибок для функции интегрирования. (Префикс INT_ говорит, что эти коды относятся к интегрированию). Функция интегрирования имеет дополнительный формальный параметр, через который происходит передача кода ошибки. Отметим, что одним из возможных кодов являеется INT_OK — значение, соответствующее успешному выполнению функции.
Теперь посмотрим, как может быть реализована функция integrate.
#include "integrate.h"
double integrate(RealFun f, double a, double b, double eps, ErrorCode *perr) {
double result = 0.0;
if(perr != NULL)
*perr = INT_OK; // По умолчанию считаем, что ошибок нет
// ...
if(/* условие возникновения ошибки */) {
if(perr)
*perr = INT_CONVERGENCE;
return result;
}
// ...
return result;
}
Сначала проверяем, что значением perr явлется ненулевой адрес. Это
означает, что вызывающая сторона хочет получить информацию об
ошибках. Если в процессе вычислений наступает определенное
условие, то по адресу perr записывается код ошибки и управление
передается вызывающей функции.
Пример использования.
#include "integrate.h"
int main() {
ErrorCode err;
// Вызов с проверкой
double res = integrate(sin, 0, 1, 0.001, &err);
switch(err) {
case INT_LIMITS:
printf("Ошибка интегрирования. Неверные педелы. Код ошибки: %d\n", err);
return -1;
// ... обработка других кодов
case INT_OK:
// эту ветку можно не указывать
break;
}
// Вызов БЕЗ ПРОВЕРКИ
res = integrate(sin, 0, 1, 0.001, NULL);
return 0;
}
Заметим, что если проверка на ошибки не требуется, то можно не объявлять переменную типа ErrorCode.
Использование перечисления (enum) для определения кодов ошибок позволяет в тексте программы писать вместо числовых констант символьные имена, что должно упрощать понимание кода при последующем его прочтении.