Каким должен быть язык программирования? Анализ и критика Описание языка Компилятор
Отечественные разработки Cтатьи на компьютерные темы Компьютерный юмор Новости и прочее

О тестах, доказывающих отсутствие ошибок

«Тест может показать наличие ошибок, но ни один тест не может доказать их отсутствия»

Э. Дейкстра

На первый взгляд утверждение Дейкстры неопровержимо. Действительно, поди-ка докажи, что в программе X, написанной на языке Y, нет ошибок. Компания, разрабатывающая PVS-Studio, инструмент для обнаружения ошибок в исходных текстах на Си, C++, C# и Java, регулярно знакомит нас с коллекциями ошибок в известном ПО. Они придумывают всё новые и новые методы обнаружения. Но ошибки не собираются сдаваться.

Производителям PVS-Studio можно задать вопрос: а готовы ли они дать юридически обязывающие гарантии отсутствия ошибок в ПО, чтобы ПО можно было застраховать, а в случае обнаружения ошибок подать суд? Ответ всем очевиден.

Казалось бы, тут безнадёга и полная обречённость. Ошибки неистребимы. Однако всё-таки попробуем опровергнуть Дейкстру. Или показать очевидность того, что в ряде случаев это возможно.

Как доказать отсутствие ошибок в чистых функциях

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

// Функция, которая принимает восьмибитный символ,
// а возвращает «истину» или «ложь»
bool  is_digit (unsigned char  c) {
	return '0' <= c && c <= '9';
}

Поскольку восьмибитный параметр имеет всего 256 вариаций своего значения, то проверить и получить доказательство отсутствия ошибок можно, сделав 256 проверок. Для этого надо просто перебрать все возможные значения:

c = 0; if (is_digit (c) != false) { printf("Error, c=%d", c); exit; }
. . .
c = 47; if (is_digit (c) != false) { printf("Error, c=%d", c); exit; }
c = 48; if (is_digit (c) != true) { printf("Error, c=%d", c); exit; }
. . .
c = 57; if (is_digit (c) != true) { printf("Error, c=%d", c); exit; }
c = 58; if (is_digit (c) != false) { printf("Error, c=%d", c); exit; }
. . .
c = 255; if (is_digit (c) != false) { printf("Error, c=%d", c); exit; }

Здесь и далее приведено решение «в лоб», которое хорошо иллюстрирует, но на практике не оптимально.

Чтобы считать доказанным отсутствие ошибок в функции is_digit, необходимо её вызвать со всеми вариациями её параметра, получить все значения результата и сравнить с эталоном. Эта задача трудоёмка, но её можно автоматизировать. Можно сделать программу, которая автоматически сгенерирует вызовы функции со всеми возможными параметрами, потом вызовет её, сохранит результаты и сравнит с эталоном. Конечно, можно задать вопрос: «А кто доказал отсутствие ошибок в проверяющей программе?», но мы не будем скатываться в схоластические споры.

Рассмотренная выше функция is_digit удобна для нас тем, что у неё всего лишь 256 вариаций значения. Если тип входного параметра поменять на int, то для 32-разрядных систем число вариаций составит 4 миллиарда с хвостиком. А если этих параметров несколько, то комбинаций, которые надо проверить, становится просто нереально много, если бы проверками занимался человек. Да и проверяющей программе это может быть не по плечу, если результат каждого вызова хранить в оперативной памяти. Но, к счастью, хранить всё и не надо. Можно проверять до первой ошибки, ведь нам интересно, при каких параметрах она случилась. А если первой ошибки не было, значит их вообще не было и все результаты верны. Тогда отсутствие ошибок в функции можно считать доказанным.

Мы немножко поколебали пьедестал, на котором стоит Дейкстра. Но мы помним, что существует доказательство о ненужности программ, состоящих только из чистых функций. А как обстоят дела с нечистыми?

Как доказать отсутствие ошибок в недетерминированных функциях

Недетерминированные функции в общем случае выдают разные результаты для одних и тех же входных параметров. Как доказать тестами отсутствие ошибок? Рассмотрим такой пример. Нам надо написать недетерминированную функцию, которая читает системную дату и выдаёт порядковый номер дня недели:

unsigned int  get_day_of_week () {
	unsigned int  day = get_system_date ();
	return day % 7;	// будем считать, что эпоха началась с понедельника
}

Рассмотрим недетерминированную функцию get_system_date () повнимательнее. Какого типа результат она выдаёт? Типа unsigned int. Что мы можем с этим сделать? План такой.

  • Вынесем недетерминированную функцию за пределы нашей функции. Её адрес просто передаётся извне внутрь нашей функции в качестве параметра. Внутри нашей функции мы даже не знаем, что она из себя представляет, знаем лишь тип результата, который она возвращает. Мы избавляем свою функцию от нечистоты, оставив её снаружи.
  • Пишем другую функцию, аналогичную по сигнатуре недетерминированной. Её задача — симулировать работу get_system_date (), перебрав все возможные значения unsigned int.
  • Как и в случае с чистыми функциями, можно сгенерировать тесты для всех возможных значений, которые может возвратить get_system_date (). Отсутствие ошибок в тестах будет говорить о доказанном отсутствии ошибок в вызывающей функции. Ведь недетерминированную функцию мы убрали вовне, не правда ли?
static unsigned int value = 0;
unsigned int simulator () {
	unsigned int  ret = value;
	++ value;
	return ret;
}
typedef  unsigned int (*fn_ptr) ();
// новая версия  get_day_of_week
unsigned int  get_day_of_week (fn_ptr  fun) {
	unsigned int  day = fun ();
	return day % 7;
}
А теперь тесты:
value = 0; if ( get_day_of_week (simulator) != 0) {
	printf("Error, value=%d", value); exit; }
value = 1; if ( get_day_of_week (simulator) != 1) {
	printf("Error, value=%d", value); exit; }
. . .
value = MAX_INT; if ( get_day_of_week (simulator) != <нечто>) {
	printf("Error, value=%d", value); exit; }

В данном случае мы занимаем такую позицию: «Я не знаю, правильно ли работает чужая недетерминированная функция. Не я её написал. Но я знаю, что моя функция работает правильно и я это доказал. Я за неё ручаюсь». Это ещё один камешек в огород Дейкстры.

Недетерминированными могут быть не только функции, но и какие-то внешние по отношению к нашей функции переменные. Например, какая-то внешняя переменная непредсказуемо изменяет своё значение, это изменение может делаться операционной системой или параллельным потоком. В этом случае для нашей функции меняется немного. Просто ей передаётся параметром не адрес недетерминированной функции, а адрес недетерминированной переменной. А раз так, то для проведения тестов можно передать функции адрес переменной-симулятора.

Как доказать отсутствие ошибок в функциях с побочными эффектами

Напишем функцию, которая производит побочный эффект, которая получает как параметр дату и записывает порядковый номер дня недели.

void  set_day_of_week (unsigned int  in) {
	unsigned int  day = in % 7;
	put (day);	// вывод информации, put производит побочный эффект
}
По аналогии с предыдущим примером функцию, производящую побочный эффект, выносим за пределы нашей функции, пишем функцию-симулятор и в тестах нашей функции передаём адрес симулятора.
static unsigned int value;
void simulator (unsigned int  arg) {
	value = arg;
}
typedef  void (*fn_ptr) (unsigned int);
unsigned int  set_day_of_week (fn_ptr  fun) {
	unsigned int  day = in % 7;
	fun (day);
}
Тесты:
set_day_of_week (simulator(0)); if ( value != 0) {
	printf("Error, input=0, output=%d", value); exit; }
set_day_of_week (simulator(1)); if ( value != 1) {
	printf("Error, input=1, output=%d", value); exit; }
. . .
set_day_of_week (simulator(MAX_INT)); if ( value != <нечто>) {
	printf("Error, input=MAX_UINT, output=%d", value); exit; }

И здесь мы формулируем свою позицию: «Не буду доказывать безошибочность чужой функции с побочным эффектом. Но безошибочность моей функции я доказал».

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

Как доказать отсутствие ошибок в недетерминированных функциях с побочными эффектами

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

void  get_and_set_day_of_week () {
	unsigned int  day = get_system_date ();
	day %= 7;
	put (day);	// вывод информации, put производит побочный эффект
}
По аналогии с предыдущими примерами нечистые функции выносим за пределы, пишем симулирующие функции, а в тестах наша функция получит адреса симуляторов.
static unsigned int in_value = 0;
static unsigned int out_value;

unsigned int in_simulator () {
	unsigned int  ret = in_value;
	++ in_value;
	return ret;
}
void out_simulator (unsigned int  arg) {
	out_value = arg;
}

typedef  unsigned int (*in_fn_ptr) ();
typedef  void (*out_fn_ptr) (unsigned int);

unsigned int  get_and_set_day_of_week (in_fn_ptr  in_fun, out_fn_ptr  out_fun) {
	unsigned int  day = in_fun () % 7;
	out_fun (day);
}
Тесты:
in_value = 0;
get_and_set_day_of_week (in_simulator, out_simulator);
if ( out_value != 0) {
	printf("Error, input=%d, output=%d", in_value, out_value); exit; }
in_value = 1;
get_and_set_day_of_week (in_simulator, out_simulator);
if ( out_value != 1) {
	printf("Error, input=%d, output=%d", in_value, out_value); exit; }
. . .
in_value = MAX_UINT;
get_and_set_day_of_week (in_simulator, out_simulator);
if ( out_value != <нечто>) {
	printf("Error, input=%d, output=%d", in_value, out_value); exit; }

Здесь опять наша позиция такова: «Не буду доказывать безошибочность чужих нечистых функций, но моя функция ошибок не содержит. Я это доказал».

Послесловие

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

Да, в голову могут прийти каверзные вопросы. Например такие:

  • Как тестировать функции, у которых параметром является текст? Можно ли доказать безошибочность компилирующих функций, если длина текста мега- и гигабайты?
  • Как тестировать функции, у которых параметром является число с плавающей точкой? Как охватить весь диапазон чисел чисел, как перейти от текущего параметра с плавающей точкой к следующему?

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

Веру в слова Дейкстры, надеюсь, хоть немного пошатнули. Но есть ещё слова Страуструпа:

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

А мы на примерах выше добились успехов, как раз внося ограничения. И кто тут прав?

Post scriptum

После предварительного обсуждения сделал вывод, что нужно чётче расставить акценты.

Существует целое направление в программировании как науке — доказательство корректности программ. Доказательство достигается через верификацию, анализ исходных текстов. К примеру, PVS-Studio так и работает: анализирует, находит ошибки. Но вот доказать безошибочность не может.

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

Для тестирования передаваемые в параметрах адреса нечистых функций заменяются на адреса их симуляторов, а далее делаются вызовы с полным перебором входных параметров. Получение неправильных результатов означает, что в «чёрном ящике» есть ошибка, её надо исправить. И так до тех пор, пока не исчезнут.

Из-за того, что «чёрный ящик» является чистой функцией, он не может выдать разные результаты для одинаковых входных параметров. Это гарантирует, что этот ящик (если его не трогали) не может сломаться в дальнейшем.

Опубликовано: 2023.01.21, последняя правка: 2023.09.30    20:45

ОценитеОценки посетителей
   ██████████████████████████████████████████ 1 (100%)
   ▌ 0
   ▌ 0
   ▌ 0

Отзывы

     2023/01/24 22:49, Неслучайный читатель          # 

Зарубки на память, чтоб не забыли про овраги.
  • В примерах выше нет примеров для функций, которые одновременно и недереминированные, и с побочным эффектом. Например, чтение из файла 1 байта (недетерминированность) приводит к смещению на 1 позицию для чтения (побочный эффект).
  • Выше недетерминированность встречается в начале функции, а побочный эффект — в конце. А если они в средине?
  • А что если они встречаются в цикле?
  • А если встречается рекурсия?
Доказательство через тестирование, конечно, заманчиво. Но надо учесть все нюансы, в том числе мои вопросы.

     2023/01/24 23:37, Gudleifr          # 

Правильно ли работает чистая функция "вернуть 1, если предъявленная ей Машина Тьюринга остановится"?

     2023/01/25 12:57, Автор сайта          # 

Неслучайный читатель
Да, надо обдумать.

Gudleifr
Я вам не скажу за всю Одессу машину Тьюринга. Описанное тестирование не ставит своей задачей доказать безошибочность внешних функций — операционной системы или внешних библиотек. Но позволяет абстрагироваться от них, отвязать свои вычисления от чужих. При тестировании чужие функции не вызываются, поэтому не важно, глохнет ли машина Тьюринга, зацикливается ли функция WinAPI DispatchMessage, — они не будут вызваны. Вместо них будут работать функции-симуляторы, которые либо перебирают значения, либо фиксируют выводимые данные. Думаю, Вы имели в виду отладку, когда бы запускалось приложение целиком в реальном окружении. Я же предложил тестирование каждой функции в отдельности в искусственной среде.

     2023/01/25 13:51, Gudleifr          # 

Думаю, Вы имели в виду отладку, когда бы запускалось приложение целиком в реальном окружении

Нет, я имел в виду проблему остановки. Из неразрешимости которой следует невозможность полного тестирования.

     2023/01/25 23:50, Автор сайта          # 

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

Сама тестируемая функция зациклиться может. Тогда долгое отсутствие результата — это признак наличия ошибки. Это повод к тому, чтобы вернуться к работе над ошибками. Такая же реакция, как и в случае явного указания на ошибку.

     2023/01/25 23:56, Gudleifr          # 

На вход тестируемой функции подаются перебираемые параметры, на выходе проверяются результаты

Ок. Подаю на вход описание Машины Тьюринга. Жду...

     2023/01/26 16:04, Автор сайта          # 

Пример, приведённый выше:
bool  is_digit (unsigned char  c) {
if ('0' <= c && c <= '9') return true;
return false;
}
Видите, на вход функции подаётся unsigned char c. Если подать на вход объект «Машина Тьюринга», то он будет отвергнут компилятором по формальным (не говоря уже о других) основаниям: он не имеет тип unsigned char. Поэтому проблема остановки того, чего не будет в программе, просто не возникнет.

Жду

— Рабинович, как ваше здоровье?
— Не дождетесь!

     2023/01/26 16:16, Gudleifr          # 

Если подать на вход объект «Машина Тьюринга», то он будет отвергнут компилятором по формальным (не говоря уже о других) основаниям: он не имеет тип unsigned char

Не надо играть в обозначения. Вы согласны, что одна Машина Тьюринга не может заранеее судить о поведении другой Машины Тьюринга? Если не согласны, идите спорьте с Тьюрингом. Если согласны, признайте, что полное тестирование невозможно.

     2023/01/26 21:47, Автор сайта          # 

Вот таблица «входные параметры — результат» (по вертикали — старшие биты, по горизонтали — младшие) для is_digit:
   _0 _1 _2 _3 _4 _5 _6 _7 _8 _9 _A _B _C _D _E _F
0_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
1_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
2_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
3_ 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0
4_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
5_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
6_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
7_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
8_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
9_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
A_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
B_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
C_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
D_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
E_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
F_ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Правильность доказана? Доказана. Упорствующие могут ссылаться на недоказанность правильности работы компилятора, операционной системы и процессора, на машину Тьюринга, на тезис Чёрча («правильность или неправильность формальной арифметики недоказуемы средствами самой формальной арифметики»), на законы Паркинсона и британских учёных.

Однако это не отменит того факта, что правильность is_digit доказана. И было бы неплохо иметь хорошие средства тестирования, которые могли предъявлять такие подобные доказательства. Доказательства не для научных журналов. А для себя или для заказчика.

     2023/01/26 22:04, Gudleifr          # 

Правильность доказана?

Разумеется, нет. В зависимости от контекста, цифрами могут считаться и буквы A-Z...

Вот, например, аналогичная таблица из одной моей программы:
 @1251@	DW 0FF00h, 0FF01h, 0FF02h, 0FF03h, 0FF04h, 0FF05h, ... 0FF0Fh
DW 0FF10h, 0FF11h, 0FF12h, 0FF13h, 0FF14h, 0FF15h, ... 0FF1Fh
DW 0FF20h, 0FF21h, 0FF22h, 0FF23h, 0FF24h, 0FF25h, ... 0FF2Fh
DW 00030h, 00131h, 00232h, 00333h, 00434h, 00535h, ... 0FF3Fh
DW 0FF40h, 00A41h, 00B42h, 00C43h, 00D44h, 00E45h, ... 0184Fh
DW 01950h, 01A51h, 01B52h, 01C53h, 01D54h, 01E55h, ... 0FF5Fh
DW 0FF60h, 00A41h, 00B42h, 00C43h, 00D44h, 00E45h, ... 0184Fh
DW 01950h, 01A51h, 01B52h, 01C53h, 01D54h, 01E55h, ... 0FF7Fh
DW 0FF80h, 0FF81h, 0FF82h, 0FF83h, 0FF84h, 0FF85h, ... 0FF8Fh
DW 0FF90h, 0FF91h, 0FF92h, 0FF93h, 0FF94h, 0FF95h, ... 0FF9Fh
DW 0FFA0h, 0FFA1h, 0FFA2h, 0FFA3h, 0FFA4h, 0FFA5h, ... 0FFAFh
DW 0FFB0h, 0FFB1h, 0FFB2h, 0FFB3h, 0FFB4h, 0FFB5h, ... 0FFBFh
DW 0FFC0h, 0FFC1h, 0FFC2h, 0FFC3h, 0FFC4h, 0FFC5h, ... 0FFCFh
DW 0FFD0h, 0FFD1h, 0FFD2h, 0FFD3h, 0FFD4h, 0FFD5h, ... 0FFDFh
DW 0FFC0h, 0FFC1h, 0FFC2h, 0FFC3h, 0FFC4h, 0FFC5h, ... 0FFCFh
DW 0FFD0h, 0FFD1h, 0FFD2h, 0FFD3h, 0FFD4h, 0FFD5h, ... 0FFDFh
Которая из двух правильнее?

     2023/01/26 22:58, Автор сайта          # 

В зависимости от контекста, цифрами могут считаться и буквы A-Z.

Не знаком с таким контекстом. А классики пишут так:

Isdigit(c) — это функция в языке Си, которая может использоваться для проверки того, является ли переданный символ цифрой или нет. Он возвращает ненулевое значение, если это цифра, иначе он возвращает 0. Например, он возвращает ненулевое значение для '0' — '9' и ноль для других.

В определении функции is_digit написано, что она имеет входной тип unsigned char, т. е. размер аргумента 1 байт. А вы поднимаете вопрос о двухбайтовом аргументе. Для этого случая нужна другая функция. А is_digit, которая для однобайтового unsigned char, свою задачу решает безупречно, о чём свидетельствует табличка.

     2023/01/26 23:07, Gudleifr          # 

А классики пишут так...

Или так: цифрой является все от нуля до текущего значения BASE, читая 0-9, A-Z...

Isdigit(c) — это функция в языке Си

Опять ошибка. Не в Си, а в POSIX.

А вы поднимаете вопрос о двухбайтовом аргументе

Да, мне так удобнее. И моя таблица также решает задачу безупречно.

Вы просто запутались. Методы программирования масштабированием и структурный — это два разных метода. В первом применяется тестирование и нет доказательств правильности. Во втором — наоборот, но в нем надо доказывать все с самого начала, явно описывая контексты.

     2023/03/17 01:08, Comdiv          # 

Вот мало было того, что тема — это наброс, так ещё и модельный пример и ошибочный, и говнокод из-за тавтологии.
bool  is_digit (unsigned char  c) {
if (c <= '0' && c <= '9') return true;
return false;
}
Без ненужного раздувания он должен был выглядеть так:
bool  is_digit (unsigned char  c) {
return '0' <= c && c <= '9';
}

     2023/03/17 15:38, Автор сайта          # 

Давно Вы сюда не заглядывали.

Вы правы, мой код содержит ошибку. Исправил.

тема — это наброс

Тема — не наброс, а стремление увеличить надёжность программ (что вытекает из «философии»), а для этого все средства хороши. От того, что опровергается какой-то постулат на каком-то подмножестве случаев, Дейкстра не будет втоптан в грязь, а я не вознесусь вместо него ввысь.

Есть такая техника тестирования — фаззинг, когда программы проверяются как можно большим числом вариантов параметров. Да, серьёзно помогает в поиске ошибок. Но не гарантирует отсутствие ошибок. Хотя бы из-за наличия побочных эффектов. Моё предложение — обеспечить чистоту функции и после этого можно доказывать её безошибочность. А корректность вынесенных наружу нечистых функций (а это — ввод/вывод, который зависит от ОС) — это уже не моя забота.

     2023/03/17 17:06, Неслучайный читатель          # 

Кстати, не обязательно менять код функций, передавая нечистые функции как параметры. Достаточно сделать подмену библиотек. Вместо
#include <библиотека ввода/вывода>
подставить
#include "библиотека с симулякрами"
где функции с такими же именами и сигнатурой, как и у функций ввода/вывода, обеспечивают перебор параметров и фиксируют результаты.

     2023/03/17 19:45, Comdiv          # 

Я довольно часто сюда заглядываю, просто не считаю нужным высказываться по любому поводу. Вы пишете, что это не наброс, потому что имеете в виду цель, а я имею в виду результат. «Хотели как лучше, получилось как всегда». Дейкстра докапывался до сути, вы докапываетесь до слов. Вы сами написали про то, что количество вариантов быстро растёт, но затем смазали это словами о том, что это проблема для человека или если результаты хранить в памяти. Для начала протестируйте сложение двух 32-разрядных переменных и посчитайте хотя бы сколько энергии для этого нужно. Если этого покажется мало, потестируйте 64-разрядные.

     2023/03/20 10:46, Автор сайта          # 

Два 32-разрядных параметра дадут 264 варианта. Просто взрывной рост. Проверить всё — человеку такое не по силам. Поэтому проверку результатов надо автоматизировать. Самый очевидный вариант — результаты сохранять в файлы, а потом отдельной программой проверять. Но 264 варианта даже на диске сохранить трудно.

Есть другой вариант. Можно дублировать функции, для этого как раз пригодится вынесение нечистоты за пределы функций (Это ответ на предложение о подмене библиотечных функций их симуляторами). Дублирующие функции может написать кто-то другой, чтобы соблюсти чистоту эксперимента.

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

     2023/03/20 18:41, Comdiv          # 

Вы продолжаете настаивать на том, что это не под силу всего лишь человеку. Пожалуйста, проведите эксперимент — проверить сложение двух 32-битных чисел не так сложно.

     2023/03/22 17:59, Автор сайта          # 

Берём 2 цикла для двух переменных, один внутри другого. Если цикл полного перебора для одной переменной занимает 1 секунду, то цикл для другой переменной будет выполняться более 100 лет. Доказательство правильности за 100 лет перестаёт быть актуальным. При добавлении третьей переменной необходимое время превысит возраст вселенной. Да, не очень оптимистично :( Даже «очень не очень».

Добавить свой отзыв

Написать автору можно на электронную почту
mail(аt)compiler.su

Авторизация

Регистрация

Выслать пароль

Карта сайта


Содержание

Каким должен быть язык программирования?

Анализ и критика

●  Устарел ли текст как форма представления программы

●  Русский язык и программирование

●  Многоязыковое программирование

Синтаксис языков программирования

Синтаксический сахар

●  Некоторые «вкусности» Алгол-68

●  «Двухмерный» синтаксис Python

●  Почему языки с синтаксисом Си популярнее языков с синтаксисом Паскаля?

●  Должна ли программа быть удобочитаемой?

●  Стиль языка программирования

●  Тексто-графическое представление программы

●●  Разделители

●●  Строки программы

●●  Слева направо или справа налево?

●  Комментарии

●●  Длинные комментарии

●●  Короткие комментарии

●●  Комментарии автоматической генерации документации

●●  Нерабочий код

●●  Помеченные комментарии

●  Нужны ли беззнаковые целые?

●  Шестнадцатиричные и двоичные константы

●  Условные операторы

●  Переключатель

●  Циклы

●●  Продолжение цикла и выход из него

●  Некошерный «goto»

●  Изменение приоритетов операций

●  Операции присвоения и проверки на равенство. Возможно ли одинаковое обозначение?

●  Так ли нужны операции «&&», «||» и «^^»?

●  Постфиксные инкремент и декремент

●  Почему в PHP для конкатенации строк используется «.»?

●  Указатели и ссылки в C++

●●  О неправомерном доступе к памяти через указатели

●  Обработка ошибок

●  Функциональное программирование

●●  Нечистые действия в чистых функциях

●●  О чистоте и нечистоте функций и языков

●●  Макросы — это чистые функции, исполняемые во время компиляции

●●  Хаскелл, детище британских учёных

●●  Измеряем замедление при вызове функций высших порядков

●●  C vs Haskell: сравнение скорости на простом примере

●●  Уникальность имён функций: за и против

●●  Каррирование: для чего и как

●●  О тестах, доказывающих отсутствие ошибок

●  Надёжные программы из ненадёжных компонентов

●●  О многократном резервировании функций

●  Использование памяти

●  Почему динамическое распределение памяти — это плохо

●  Как обеспечить возврат функциями объектов переменной длины?

●●  Типы переменного размера (dynamically sized types, DST) в языке Rust

●●  Массивы переменной длины в C/C++

●●  Размещение объектов в стеке, традиционный подход

●●  Размещение объектов переменной длины с использованием множества стеков

●●  Размещение объектов переменной длины с использованием двух стеков

●●  Реализация двухстековой модели размещения данных

●●  Двухстековая модель: тесты на скорость

●●  Изменение длины объекта в стеке во время исполнения

●●  Размещение объектов переменной длины с использованием одного стека

●  Можно ли забыть о «куче», если объекты переменной длины хранить в стеке

●  Безопасность и размещение объектов переменной длины в стеке

●  Массивы, структуры, типы, классы переменной длины

●  О хранении данных в стеке, вместо заключения

●  Реализация параметрического полиморфизма

Описание языка

Компилятор

Отечественные разработки

Cтатьи на компьютерные темы

Компьютерный юмор

Новости и прочее




Последние отзывы

2024/04/25 21:05 ••• Ttimofeyka
Энтузиасты-разработчики компиляторов и их проекты

2024/04/23 00:00 ••• alextretyak
Признаки устаревшего языка

2024/04/21 00:00 ••• alextretyak
Постфиксные инкремент и декремент

2024/04/20 21:28 ••• Бурановский дедушка
Русский язык и программирование

2024/04/07 15:33 ••• MihalNik
Все языки эквивалентны. Но некоторые из них эквивалентнее других

2024/04/01 23:39 ••• Бурановский дедушка
Новости и прочее

2024/04/01 23:32 ••• Бурановский дедушка
Русской операционной системой должна стать ReactOS

2024/03/22 20:41 ••• void
Раскрутка компилятора

2024/03/20 19:54 ••• kt
О многократном резервировании функций

2024/03/20 13:13 ••• Неслучайный читатель
Надёжные программы из ненадёжных компонентов

2024/03/07 14:16 ••• Неслучайный читатель
«Двухмерный» синтаксис Python

2024/03/03 16:49 ••• Автор сайта
О неправомерном доступе к памяти через указатели

2024/02/28 18:59 ••• Вежливый Лис
Про лебедей, раков и щук