C: Как Вы моделируете 'исключение'?

Существует относительно новый сайт, запускающийся в nhibernate.info, который имеет целью быть категорическим источником для NHibernate-связанной документации.

14
задан Malcolm 8 January 2013 в 10:17
поделиться

11 ответов

Подобное исключениям поведение в C достигается через setjmp / longjmp . Однако на самом деле вам нужен код ошибки. Если все значения потенциально могут быть возвращены, то вы можете принять выходной параметр в качестве указателя и использовать его для возврата значения, например:

int pop(double* outval)
{
        if(outval == 0) return -1;
        if(sp > 0)
                *outval = val[--sp];
        else {
                printf("error: stack empty\n");
                return -1;
        }
        return 0;
}

Не идеально, очевидно, но таковы ограничения C.

Кроме того, если вы пойдете по этому пути, вы можете определить символические константы для кодов ошибок (или использовать некоторые из стандартных ), чтобы пользователь мог различать «стек пуст» и « ты дал мне нулевой указатель, тупица ".

15
ответ дан 1 December 2019 в 06:24
поделиться

Один из подходов - указать, что pop () имеет неопределенное поведение, если стек пуст. Затем вы должны предоставить функцию is_empty (), которую можно вызвать для проверки стека.

Другой подход - использовать C ++, который имеет исключения: -)

4
ответ дан 1 December 2019 в 06:24
поделиться

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

В C доступны следующие механизмы:

  • Нелокальные переходы с setjmp / longjmp
  • Сигналы

Однако ни один из них не имеет семантики, отдаленно напоминающей исключения C # (или C ++).

2
ответ дан 1 December 2019 в 06:24
поделиться

вы можете вернуть указатель на double:

  • не NULL -> действительный
  • NULL -> недопустимый
1
ответ дан 1 December 2019 в 06:24
поделиться

1) Вы возвращаете значение флага, чтобы показать, что он потерпел неудачу, или используете синтаксис TryGet, где возврат является логическим для успеха, а значение передается через выходной параметр.

2 ) Если это работает под Windows, существует чистая форма исключений на уровне C на уровне ОС, называемая структурной обработкой исключений, с использованием синтаксиса типа «_try». Я упоминаю об этом, но не рекомендую в данном случае.

1
ответ дан 1 December 2019 в 06:24
поделиться

Вы можете построить систему исключений поверх longjmp / setjmp: Исключения в C с Longjmp и Setjmp . Это действительно работает довольно хорошо, и статью тоже стоит прочитать. Вот как мог бы выглядеть ваш код, если бы вы использовали систему исключений из связанной статьи:

  TRY {
    ...
    THROW(MY_EXCEPTION);
    /* Unreachable */
  } CATCH(MY_EXCEPTION) {
    ...
  } CATCH(OTHER_EXCEPTION) {
    ...
  } FINALLY {
    ...
  }

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

longjmp / setjmp переносимы: C89, C99 и POSIX.1-2001 указывают setjmp () .

Обратите внимание, однако, что исключения, реализованные таким образом, по-прежнему будут иметь некоторые ограничения по сравнению с «реальными» исключениями в C # или C ++. Основная проблема заключается в том, что только ваш код будет совместим с этой системой исключений. Поскольку в C нет установленного стандарта для исключений, системные и сторонние библиотеки просто не будут оптимально взаимодействовать с вашей собственной системой исключений. Тем не менее, иногда это может оказаться полезным хаком.

Я не рекомендую использовать это в серьезном коде , с которым должны работать программисты, кроме вас самих. С этим слишком легко выстрелить себе в ногу, если ты точно не знаешь, что происходит. Распределение потоков, управление ресурсами и обработка сигналов - это проблемные области, с которыми неигровые программы могут столкнуться, если вы попытаетесь использовать longjmp «исключения».

Я рекомендую использовать это в серьезном коде , с которым должны работать не вы, а другие программисты. С этим слишком легко выстрелить себе в ногу, если ты не знаешь точно, что происходит. Распределение потоков, управление ресурсами и обработка сигналов - это проблемные области, с которыми неигровые программы могут столкнуться, если вы попытаетесь использовать "исключения" longjmp.

Я рекомендую использовать это в серьезном коде , с которым должны работать не вы, а другие программисты. С этим слишком легко выстрелить себе в ногу, если ты не знаешь точно, что происходит. Распределение потоков, управление ресурсами и обработка сигналов - это проблемные области, с которыми неигровые программы могут столкнуться, если вы попытаетесь использовать longjmp «исключения».

10
ответ дан 1 December 2019 в 06:24
поделиться

В подобных случаях вы обычно делаете одно из

  • . Оставьте это на усмотрение вызывающего абонента. например, вызывающий должен знать, безопасно ли использовать pop () (например, вызвать функцию stack-> is_empty () перед извлечением стека), и если вызывающий совершает ошибку, это его вина и удачи.
  • Signal ошибка через выходной параметр или возвращаемое значение.

например, вы выполняете либо

double pop(int *error)
{
  if(sp > 0) {
      return val[--sp];
      *error = 0;
  } else {
     *error = 1;
      printf("error: stack empty\n");
      return 0.0;
  }

}

, либо

int pop(double *d)
{
   if(sp > 0) { 
       *d = val[--sp];
       return 0;
   } else {
     return 1;

   }
}
3
ответ дан 1 December 2019 в 06:24
поделиться

setjmp , longjmp и макросы. Это делалось сколько угодно раз - самая старая реализация, о которой я знаю, принадлежит Эрику Робертсу и Марку Вандервурду, - но та, которую я использую в настоящее время, является частью интерфейсов и реализаций C Дейва Хэнсона и бесплатна для Принстона.

0
ответ дан 1 December 2019 в 06:24
поделиться

У вас есть несколько вариантов:

1) Значение магической ошибки. Не всегда достаточно хорошо по причине, которую вы описываете. Я предполагаю, что теоретически для этого случая вы могли бы вернуть NaN, но я не рекомендую этого делать.

2) Определите, что нельзя выталкивать, когда стек пуст. Затем ваш код либо просто предполагает, что он непустой (и становится неопределенным, если это так), либо утверждает.

3) Измените подпись функции, чтобы вы могли указать успех или неудачу:

int pop(double *dptr)
{
    if(sp > 0) {
            *dptr = val[--sp];
            return 0;
    } else {
            return 1;
    }
}

Задокументируйте ее как «Если успешно, возвращает 0 и записывает значение в место, на которое указывает dptr. В случае сбоя возвращает ненулевое значение. "

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

4) Передайте объект «исключение» в каждую функцию по указателю и запишите ему значение в случае ошибки. Затем вызывающий абонент проверяет его или нет в зависимости от того, как они используют возвращаемое значение. Это очень похоже на использование «errno», но без значения для всего потока.

5) Как говорили другие, реализуйте исключения с помощью setjmp / longjmp. Это выполнимо, но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает обработку типичных ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

4) Передать объект «исключение» в каждую функцию по указателю и записать в него значение в случае ошибки. Затем вызывающий абонент проверяет его или нет в зависимости от того, как они используют возвращаемое значение. Это очень похоже на использование «errno», но без значения для всего потока.

5) Как говорили другие, реализуйте исключения с помощью setjmp / longjmp. Это выполнимо, но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает типичную обработку ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

4) Передать объект «исключение» в каждую функцию по указателю и записать в него значение в случае ошибки. Затем вызывающий абонент проверяет его или нет в зависимости от того, как они используют возвращаемое значение. Это очень похоже на использование "errno", но без значения для всего потока.

5) Как говорили другие, реализуйте исключения с помощью setjmp / longjmp. Это выполнимо, но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает типичную обработку ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

Затем вызывающий абонент проверяет его или нет в зависимости от того, как они используют возвращаемое значение. Это очень похоже на использование "errno", но без значения для всего потока.

5) Как говорили другие, реализуйте исключения с помощью setjmp / longjmp. Это выполнимо, но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает обработку типичных ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

Затем вызывающий абонент проверяет его или нет в зависимости от того, как они используют возвращаемое значение. Это очень похоже на использование «errno», но без значения для всего потока.

5) Как говорили другие, реализуйте исключения с помощью setjmp / longjmp. Это выполнимо, но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает обработку типичных ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает обработку типичных ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

но требует либо передачи дополнительного параметра повсюду (цель longjmp для выполнения в случае сбоя), либо скрытия его в глобальных объектах. Это также превращает типичную обработку ресурсов в стиле C в кошмар, потому что вы не можете вызвать ничего, что могло бы выскочить за пределы вашего уровня стека, если у вас есть ресурс, за освобождение которого вы отвечаете.

7
ответ дан 1 December 2019 в 06:24
поделиться

На самом деле это прекрасный пример зла попытки перегрузить возвращаемый тип магическими значениями и просто сомнительным дизайном интерфейса.

Одно решение, которое я мог бы использовать, чтобы устранить двусмысленность (и таким образом, необходимость в «поведении, подобном исключению») в этом примере состоит в том, чтобы определить правильный тип возвращаемого значения:

struct stack{
    double* pData;
    uint32  size;
};

struct popRC{
    double value;
    uint32 size_before_pop;
};

popRC pop(struct stack* pS){
    popRC rc;
    rc.size=pS->size;
    if(rc.size){
        --pS->size;
        rc.value=pS->pData[pS->size];
    }
    return rc;
}

Использование, конечно, следующее:

popRC rc = pop(&stack);
if(rc.size_before_pop!=0){
    ....use rc.value

Это происходит ВСЕ время, но в C ++, чтобы избежать такой двусмысленности, обычно просто возвращается

std::pair<something,bool>

, где bool - индикатор успеха - посмотрите на некоторые из:

std::set<...>::insert
std::map<...>::insert

В качестве альтернативы добавьте double * к интерфейсу и верните код возврата (n UNOVERLOADED!), Скажем, перечисление, указывающее на успех.

Конечно, не нужно было возвращать размер в struct popRC . Это могло быть

enum{FAIL,SUCCESS};

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

Кстати, я искренне согласен с тем, что интерфейс стека структуры должен иметь

int empty(struct stack* pS){
    return (pS->size == 0) ? 1 : 0;
}
4
ответ дан 1 December 2019 в 06:24
поделиться

Здесь уже есть несколько хороших ответов, просто хотел упомянуть, что что-то близкое к "исключению" , может быть выполнено с помощью макроса, как это было сделано в awesome MinUnit (это возвращает только «исключение» вызывающей функции).

0
ответ дан 1 December 2019 в 06:24
поделиться
Другие вопросы по тегам:

Похожие вопросы: