Эмуляция обработки исключений в ANSI C'89

Ответить

Код подтверждения
Введите код в точности так, как вы его видите. Регистр символов не имеет значения.

BBCode ВКЛЮЧЁН
[img] ВКЛЮЧЁН
[url] ВКЛЮЧЁН
Смайлики ОТКЛЮЧЕНЫ

Обзор темы
   

Развернуть Обзор темы: Эмуляция обработки исключений в ANSI C'89

Re: Эмуляция обработки исключений в ANSI C'89

Absurd » 30 авг 2007, 21:54

sergey_kovtunenko писал(а):Речь шла о компиляторах для AVR, PIC, 8051... Я не знаю нормальных С++ компиляторов для них.
Иди по стопам Страуструпа и делай свой препроцессор для С, который разворачивает try / catch / finallly. Макросы С для этой цели не годятся совсем; Мне влом дублировать сюда все афоризмы Герба Саттера и Ко по поводу макросов. В библиотеке Qt есть например препроцессор который привязывает напрямую оконные сообщения к вызовам функций. Я мельком смотрел исходный код DOOM 3 - там с помощью ихнего отдельного препроцессора обходится другое слабое место С++ - отсутствие быстрых делегатов.
sergey_kovtunenko писал(а): То программер, который использует библиотеку получает обычные функции с обычными кодами возврата.
Это хорошо.
sergey_kovtunenko писал(а): __finally есть только в винде и такой код непереносим на другие платформы.
GDI Тоже непереносимо.
sergey_kovtunenko писал(а): Не всегда это возможно, освобождать память там, где выделялась. Это ведь одна из ключевых проблем языков вроде С++: Кто будет выделять память, а кто будет её освобождать?
Речь не о том кто должен освобождать память. Речь идет о том что память освобождается с помошью free() только в тех модулях *.c, которые ее выделили. Другие модули должны использовать destroy_other_module_entity(..). Исключения возможны только в виде функций типа _dup(), возвращаемое значение которых надо освобождать с помощью free(...)
sergey_kovtunenko писал(а): Да, но полностью самому написать систему 24\7 практически сейчас нереально, приходится использовать сторонние компоненты. А кто даёт гарантию, что там проверялись все коды ошибок, возвращаеммые функциями?
Проблемы были решены загрузкой всех сторонних компонент в рамки другого процесса и использованием RPC. Когда память выделенная под другой процесс превышала конфигурируемое значение, процесс грохался и загружался по новой. Но это неправильно.

Re: Эмуляция обработки исключений в ANSI C'89

sergey_kovtunenko » 30 авг 2007, 15:55

Absurd писал(а):Да ну. Насколько я знаю, проблема была только с MCVC++ 6 и давно мертвым Watcom. Все остальные компиляторы - Intel C++, g++, Metrowerk CodeWarrior, Digital Mars C++, KAI C++ поддреживали практически весь стандарт за исключением export template. Насчет Борланд не уверен, но насколько я знаю он тоже поддерживал стандарт намного лучше MSVC++ 6.
Речь шла о компиляторах для AVR, PIC, 8051... Я не знаю нормальных С++ компиляторов для них.
Absurd писал(а): Какой тяп-ляп? Я не хочу тянуть кучу нестандартных парадигм вместе с каждой библиотекой которую я использую. Если пишешь библиотеку, будь добр экспортируй символы через extern "C" и отлавливай все исключения на выходе, рапортуя об ошибках через возвращаемые значения.
Никто и не заставляет использовать нестандартные парадигмы. Если разрабатывать некий компонент так:

1. Внешние интерфейсы (для подключения библиотеки к другим компонентам. Возвращают обычные коды ошибок)
^
|
2. Абстрактный уровень, который отлавливает и обрабатываетошибки на уровне компонента.
^
|
3. Уровень реализации (на этом уровне выбрасываются исключения)

То программер, который использует библиотеку получает обычные функции с обычными кодами возврата.
Absurd писал(а): Если я пишу под винду на MSVC, то использую SEH явно если нужно. Например, я не оборачиваю объекты GDI в классы с деструкторами и поэтому произвожу clenup в блоке __finally.
__finally есть только в винде и такой код непереносим на другие платформы.
Absurd писал(а): Я всегда освобождаю память там же где и выделяю, поэтому функции h_rb_tree create_rb_tree(...) всегда аккомпанирует destroy_rb_tree(h_rb_tree rb_tree). Удалять сущность типа h_rb_tree с помощью free() конечно же нельзя, по меньшей мере потому что это сложная структура содержащая много элементов выделенных в динамической памяти.
Не всегда это возможно, освобождать память там, где выделялась. Это ведь одна из ключевых проблем языков вроде С++: Кто будет выделять память, а кто будет её освобождать?
Absurd писал(а): Тем кому соблазнительно системы работающие в режиме 24/7 не пишут.
Да, но полностью самому написать систему 24\7 практически сейчас нереально, приходится использовать сторонние компоненты. А кто даёт гарантию, что там проверялись все коды ошибок, возвращаеммые функциями?

Re: Эмуляция обработки исключений в ANSI C'89

Absurd » 30 авг 2007, 13:34

sergey_kovtunenko писал(а):Это изврат, а не С++. Компиляторы не могут поддерживать стандарт С++ хотя бы на каком-то уровне выше среднего, приходится бороться с особенностями конкретного компилятора С++, приходится отказываться от каких-то приёмов к которым привык в нормальных компиляторах (GCC, например). Уверен, что и код на С++ не слишком эффективный для встраиваемых систем. А для них очень критично быстродействие и затраты ресурсов. Короче куча НО.
Да ну. Насколько я знаю, проблема была только с MCVC++ 6 и давно мертвым Watcom. Все остальные компиляторы - Intel C++, g++, Metrowerk CodeWarrior, Digital Mars C++, KAI C++ поддреживали практически весь стандарт за исключением export template. Насчет Борланд не уверен, но насколько я знаю он тоже поддерживал стандарт намного лучше MSVC++ 6.
sergey_kovtunenko писал(а): У меня другая точка зрения. Для больших проектов в которых требуется высокая отказоустойчивость я бы использовал Java. А для малых и средних -- С.
Использовать язык Д считаю непрактичным, т.к. у него нет большого будущего а в настоящем, имхо, та ниша на которую он претендует уже успешно занята.
А если система реального времени? Под Джаву есть система реального времени с возможностью явного управления памятью (без уборки мусора), но она AFAIK работала на двухпроцессорном SPARC - один камень выполняет программу, другой ее шедюлит. Для i386 есть QNX, ей хратает одного процессора, но писать под нее надо на С.
sergey_kovtunenko писал(а): Ты не совсем прав. Подход который ты описал для очень неквалифицированных программистов "тяп-ляп".
Какой тяп-ляп? Я не хочу тянуть кучу нестандартных парадигм вместе с каждой библиотекой которую я использую. Если пишешь библиотеку, будь добр экспортируй символы через extern "C" и отлавливай все исключения на выходе, рапортуя об ошибках через возвращаемые значения.
sergey_kovtunenko писал(а): Во первых, почему ты не допускаешь варианта разработки какой-то части большого проекта с использованием SEH внутри компонента с интерфейсом, который возвращает привычные коды ошибок? Это самый очевидный вариант.
Если я пишу под винду на MSVC, то использую SEH явно если нужно. Например, я не оборачиваю объекты GDI в классы с деструкторами и поэтому произвожу clenup в блоке __finally.
sergey_kovtunenko писал(а): Далее, в одной из либ есть специальные макросы protectPtr(ptr, func) и unprotectPtr(ptr).
Если происходит выброс из блока try, то автоматом освобождаются все ресурсы и мемлика не наблюдается.
Я всегда освобождаю память там же где и выделяю, поэтому функции h_rb_tree create_rb_tree(...) всегда аккомпанирует destroy_rb_tree(h_rb_tree rb_tree). Удалять сущность типа h_rb_tree с помощью free() конечно же нельзя, по меньшей мере потому что это сложная структура содержащая много элементов выделенных в динамической памяти.
sergey_kovtunenko писал(а): А использование обычных кодов возврата ещё не гарантирует работу программы 24/7 . Ведь соблазнительно не анализировать их?
Тем кому соблазнительно системы работающие в режиме 24/7 не пишут.
sergey_kovtunenko писал(а): В случае SEH если не анализировать throw's, то программа будет тупо завершаться.
Этого избежать элементарно - ловить их всех в корне цикла обработки сообщений, логировать и давить.

Re: Эмуляция обработки исключений в ANSI C'89

sergey_kovtunenko » 29 авг 2007, 22:28

Absurd писал(а):Для какой конкретно нет? Многие компиляторы С++ генерируют промежуточный код, достаточно сделать мини-порт.
Это изврат, а не С++. Компиляторы не могут поддерживать стандарт С++ хотя бы на каком-то уровне выше среднего, приходится бороться с особенностями конкретного компилятора С++, приходится отказываться от каких-то приёмов к которым привык в нормальных компиляторах (GCC, например). Уверен, что и код на С++ не слишком эффективный для встраиваемых систем. А для них очень критично быстродействие и затраты ресурсов. Короче куча НО.
Absurd писал(а): Да ну? Человеку практическому который находится в рамках традиций и менстрима идея переделывать С не могла прийти в голову. А непрактический мог бы и попробовать D.
У меня другая точка зрения. Для больших проектов в которых требуется высокая отказоустойчивость я бы использовал Java. А для малых и средних -- С.
Использовать язык Д считаю непрактичным, т.к. у него нет большого будущего а в настоящем, имхо, та ниша на которую он претендует уже успешно занята.
Absurd писал(а): Я например из кода на Pure С (Без SEH) вызвал твою библиотеку, возможно даже не напрямую а опосредованно, а ты сделал "экзепшен" через нелокальный переход например или через SEH. Были бы коды возврата я бы мог закрыть хандлы файлов и освободить память. А так - Resourse Leak. В системе которая должна работать в режиме 24/7 это неизбежно приведет к отказу через несколько дней или месяцев.
Ты не совсем прав. Подход который ты описал для очень неквалифицированных программистов "тяп-ляп".
Во первых, почему ты не допускаешь варианта разработки какой-то части большого проекта с использованием SEH внутри компонента с интерфейсом, который возвращает привычные коды ошибок? Это самый очевидный вариант.

Далее, в одной из либ есть специальные макросы protectPtr(ptr, func) и unprotectPtr(ptr).
Если происходит выброс из блока try, то автоматом освобождаются все ресурсы и мемлика не наблюдается.

В другой либе попроще, просто не разрешаются return()\goto из блока try.

А использование обычных кодов возврата ещё не гарантирует работу программы 24/7 . Ведь соблазнительно не анализировать их? В случае SEH если не анализировать throw's, то программа будет тупо завершаться. При хорошем тестировании, такие "забывания" делать catch легко выявляются ;) ) Так что это очень спорный момент что ещё лучше использовать.

Re: Эмуляция обработки исключений в ANSI C'89

Absurd » 29 авг 2007, 16:49

К тому же не у всех есть компилятор С++. Его нет для встроенных систем
Для какой конкретно нет? Многие компиляторы С++ генерируют промежуточный код, достаточно сделать мини-порт.
А на Д писать безперспективно, имхо
Да ну? Человеку практическому который находится в рамках традиций и менстрима идея переделывать С не могла прийти в голову. А непрактический мог бы и попробовать D.
Так что лучше добавить функционал в язык С, тем более что я уже определился с библиотекой чужой.
Я например из кода на Pure С (Без SEH) вызвал твою библиотеку, возможно даже не напрямую а опосредованно, а ты сделал "экзепшен" через нелокальный переход например или через SEH. Были бы коды возврата я бы мог закрыть хандлы файлов и освободить память. А так - Resourse Leak. В системе которая должна работать в режиме 24/7 это неизбежно приведет к отказу через несколько дней или месяцев.

Re: Эмуляция обработки исключений в ANSI C'89

sergey_kovtunenko » 29 авг 2007, 16:13

Absurd писал(а):Ну так и пиши на С++ : кто мешает? Не нравится С++ - есть Digital Mars D.
Мне не нравиться С++, но очень нравиться С. К тому же не у всех есть компилятор С++. Его нет для встроенных систем ;) А на Д писать безперспективно, имхо.

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

Re: Эмуляция обработки исключений в ANSI C'89

Absurd » 29 авг 2007, 11:10

Ну так и пиши на С++ : кто мешает? Не нравится С++ - есть Digital Mars D.

Re: Эмуляция обработки исключений в ANSI C'89

sergey_kovtunenko » 28 авг 2007, 21:52

Absurd писал(а):Идея плохая - когда пишешь в С никаких нарушений Control Flow быть не должно. Почется по2.7183баться - генерируй Pure C код из коделетов с помощью программы на OcaML или Haskell. Пример - fftw
А мне видится отличной идеей. Если оно безопасно работает, то в чём проблема? Для чего по твоему в ANSI C присутствует хедер setjmp.h ? Для реализации нелокальных переходов при отлове ошибок глубоковложенных функций )) Так что этот хедер находит своё применение в таких библиотеках.
Оцени труд:
Реализация ООП в С
=> http://ldeniau.web.cern.ch/ldeniau/html/oopc.html

Реализации тех идей, что я хотел воплотить
=> http://ldeniau.web.cern.ch/ldeniau/html ... ption.html
=> http://www.nicemice.net/cexcept/

Re: Эмуляция обработки исключений в ANSI C'89

Absurd » 27 авг 2007, 11:24

Идея плохая - когда пишешь в С никаких нарушений Control Flow быть не должно. Почется по2.7183баться - генерируй Pure C код из коделетов с помощью программы на OcaML или Haskell. Пример - fftw

Re: Эмуляция обработки исключений в ANSI C'89

sergey_kovtunenko » 26 авг 2007, 15:44

Код: Выделить всё

/* main.c -- тестирующий библиотеку файл */

#include <stdio.h>
#include <setjmp.h>

#include "excpt.h"



void
foo(void);

void
func1(void);



void
foo(void)
{
	printf("foo()\n");
//	THROW(1);
	printf("end foo()\n");
}

int
main(void)
{
	printf("start main() : i=%d, j=%d\n", i, j);
/*	TRY {
		DBGTRYIN(main, 1);
		TRY {
			DBGTRYIN(main, 2);
			THROW (100);
			DBGTRYOUT(main, 2);
		}
		CATCH (100) {
			DBGCATCHIN(main, 2, 100);
			DBGCATCHOUT(main, 2, 100);
		}
		CATCHALL {
			DBGCATCHALLIN(main, 2);
			DBGCATCHALLOUT(main, 1);
		}
		TRYEND;
//		THROW (10);
		DBGTRYOUT(main, 1);
	}
	CATCH (10) {
		DBGCATCHIN(main, 1, 10);
//		THROW (5);
		DBGCATCHOUT(main, 1, 10);
	}
	CATCHALL {
		DBGCATCHALLIN(main, 1);
		DBGCATCHALLOUT(main, 1);
	}
	TRYEND; */
	func1();
	printf("end main() : i=%d, j=%d\n", i, j);
	return 0;
}



// Функция, в которой используются уже развёрнутые макроопределения
void
func1(void)
{
	printf("start func1() : i=%d, j=%d\n", i, j);
	do {
//		j = i; // Это логическая ошибка
		j++;
		jbuf[j].ret = setjmp(jbuf[j].jbuf);
		if (0 == jbuf[j].ret) {
			{
				DBGTRYIN(func1, 1);
				// вложенный TRY
				do {
					j = i;
					j++;
					jbuf[j].ret = setjmp(jbuf[j].jbuf);
					if (0 == jbuf[j].ret) {
						{
							DBGTRYIN(func1, 2);
							longjmp(jbuf[j].jbuf, 10);
							DBGTRYOUT(func1, 2);
						}
//						i = j; // если ты дошел до этой строчки, значит успешно (без THROW'ов, return'ов и raise()) выполнил TRY
					} else if (10 == jbuf[j].ret) {
						{
							DBGCATCHIN(func1, 2, 10);
							DBGCATCHOUT(func1, 2, 10);
						}
//						i = j;
//						j = i;
					} else {
						{
							DBGCATCHALLIN(func1, 2);
							DBGCATCHALLOUT(func1, 2);
						}
//						i = j;
//						j = i;
					}
				} while (0);
				// конец вложенному TRY
				longjmp(jbuf[j].jbuf, 100);
				DBGTRYOUT(func1, 1);
			}
			i = j;
		} else if (100 == jbuf[j].ret) {
			{
				DBGCATCHIN(func1, 1, 100);
				DBGCATCHOUT(func1, 1, 100);
			}
//			i = j;
//			j = i;
		} else {
			{
				DBGCATCHALLIN(func1, 1);
				DBGCATCHALLOUT(func1, 1);
			}
//			i = j;
//			j = i;
		}
	} while (0);
	printf("end func1() : i=%d, j=%d\n", i, j);
}

Для тех, кто дочитал до этого момента и не закрыл окно браузера, пояснение алгоритма и подводные камни.
Пояснение и различия с С++ подходом:
  • Блоки обработчиков могут быть вложенными один в один. Аналогично с С++
  • Если THROW(n) никто не обрабатывает, то программа вылетает с ошибкой. В либе реализуется за счёт передачи неверного значения в longjmp(), что крэшит программу на большинстве платформ. Полная аналогия с С++, за исключением невозможности (пока) зарегистрировать свой обработчик подобной ситуации.
  • Если внутри блока выбрасывается THROW() исключение, то оно передаётся внешнему обработчику. Почти полная аналогия с С++, кроме того, что в либе передаётся аргумент, а в С++ тот же самый аргумент передаётся во внешний блок.
  • Либа должна корректно обрабатывать ситуации, когда внутри блоков TRY\CATCH()\CATCHALL встречаются локальные переходы: goto\return()\raise(). И это одна из самых больших проблем при реализации алгоритма ;(((

В либе реализован массив значений {буффер_сохранения_состояния, код возвращаемого значения} размером в максимальную глубину вложенности. При заходе в новый блок TRY индекс массива увеличивается и запоминается новое состояние. При выходе из блока TRY индекс массива декрементируется.

Переменная j была нужна для защиты от локальных переходов и возбуждения сигналов goto\return()\raise(). Она работала так:
1. Сейчас мы находимся в i-том состоянии.
2. Делаем предположение, что наш блок TRY дойдёт нормально, без goto\return()\raise()\THROW() до самого конца. Для этого j = i+1;
3. Выполняем блок TRY.
3.1 Если блок TRY завершился успешно без исключений и переходов, то мы устойчиво стоим на новом состоянии, которое нужно "застолбить" в i, т.е. i=j
3.2 Если блок TRY завершился неудачей и исключением, то наша попытка окончилась неудачей, но мы всё равно остались стоять на i-том состоянии и переброска i = j не выполняется

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

Вернуться к началу