Управляющие операторы if

gcc 4.4.3 c89

У меня есть некоторые функции, которые инициализируют некоторое оборудование и возвращают true или false. Если false, я должен uninitialize в обратном порядке.

Однако мой код выглядит очень неопрятным со всеми операторами if.

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

Большое спасибо за любой совет,

if(init_A() == TRUE) {
 if(init_B() == TRUE) {
  if(init_C() == TRUE) {
   if(init_D() == TRUE) {
    if(init_E() == TRUE) {
     /* ALL STARTED OK */    
    }
    else {
     uninit_A();
     uninit_B();
     uninit_C();   
     uninit_D();    
    }
   }
   else {
    uninit_A();
    uninit_B();
    uninit_C();   
   }
  }
  else {
   uninit_A();
   uninit_B();
  }
 }
 else {
  /* Failed to initialize B */
  uninit_B(); 
 }
}
else {
 /* Failed to start */
}

Ответ 1

Это довольно распространенная проблема, когда шаги "init" соответствуют вещам типа malloc() или lock(), а шаги "uninit" соответствуют вещам типа free() и unlock(). Это особенно проблема, когда ресурсы должны быть освобождены в строго обратном порядке, в котором они были выделены.

Это один случай, когда использование goto оправдано:

int somefunc()
{
    int retval = ERROR;

    if (init_A() != TRUE)
        goto out_a;

    if (init_B() != TRUE)
        goto out_b;

    if (init_C() != TRUE)
        goto out_c;

    if (init_D() != TRUE)
        goto out_d;

    if (init_E() != TRUE)
        goto out_e;

    /* ALL STARTED OK */
    /* ... normal processing here ... */
    retval = OK;

    uninit_E();
  out_e:
    uninit_D();
  out_d:
    uninit_C();
  out_c:
    uninit_B();
  out_b:
    uninit_A();
  out_a:
    return retval;
}

Ответ 2

if(init_A() != TRUE) {
    goto EndA;
}
if(init_B() != TRUE) {
    goto EndB;
}
if(init_C() != TRUE) {
    goto EndC;
} 
if(init_D() != TRUE) {
    goto EndD;
}
if(init_E() != TRUE) {
    goto EndE;
} 
...
return;
EndE: uninitD();
EndD: uninitC();
EndC: uninitB();
EndB: uninitA();
EndA: return;

Ответ 3

Я бы запрограммировал массив указателей на функции, вызвал функции в цикле, а затем, если эта функция вернула false, выполните соответствующую функцию uninit_*.

Вот пример:

void (*inits[5]) (void);
void (*uninits[4]) (void);

int main(void) {
   inits[0] = init_A;
   inits[1] = init_B;
   inits[2] = init_C;
   inits[3] = init_D;
   inits[4] = init_E;

   uninits[0] = uninit_A;
   uninits[1] = uninit_B;
   uninits[2] = uninit_C;
   uninits[3] = uninit_D;

   for(int i = 0; i < 5; i++) {
      if((*inits[i])() != TRUE) {
         int j = (i < 4) ? i : 4; 
         while(j--) {
             (*uninits[j])();
         }
         break;
      }
   }
   return 1;
}

Ответ 4

BOOL a = FALSE, b = FALSE, c = FALSE, d = FALSE, e = FALSE;

if ( (a = init_A())  &&  (b = init_B())  &&  (c = init_C())  && (d = init_D())  && (e = init_E()) )
{
}
else
{
    if ( e ) uninit_E();
    if ( d ) uninit_D();
    if ( c ) uninit_C();
    if ( b ) uninit_B();
    if ( a ) uninit_A();
}

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

Ответ 5

Если ваши функции uninit_* могут определить, нужно ли им что-то делать, просто:

if (!init_A() || !init_B() || !init_C() || !init_D() )
{
  uninit_C();
  uninit_B();
  uninit_A();
  return FALSE;
}

Ответ 6

Это "обратный порядок"? Для меня обратный порядок таков:

void uninit(int from) {
    switch (from) {
        /* ... */
        case 3: uninit_C(); /* fall_through */
        case 2: uninit_B(); /* fall_through */
        case 1: uninit_A(); /* fall_through */
        case 0: break;
    }
}

И процесс init будет выглядеть следующим образом

    int count = 0;
    if (init_A()) {
        count++;
        if (init_B()) {
            count++;
            if(init_C()) {
                count++;
                if(init_D()) {
                    count++;
                    if(init_E()) {
                        count++;
                    }
                }
            }
        }
    }
    if (count == 5) /* ALL OK */;
    uninit(count);

Ответ 7

Ограниченное понимание работы C здесь, если вы решите понизить, пожалуйста, скажите мне, почему.

#include <stdio.h>

int init_a() { return 1; }; // succeed
int init_b() { return 1; }; // succeed
int init_c() { return 0; }; // fail

void uninit_a() { printf("uninit_a()\n"); }
void uninit_b() { printf("uninit_b()\n"); }
void uninit_c() { printf("uninit_c()\n"); }

typedef struct _fp {
        int (*init)();
        void (*uninit)();
} fp;

int init() {
        fp fps[] = {
                (fp){&init_a, &uninit_a},
                (fp){&init_b, &uninit_b},
                (fp){&init_c, &uninit_c}
        };

        unsigned int i = 0, j;
        for(; i < sizeof(fps) / sizeof(fp); ++i) {
                if(!(*fps[i].init)()) {
                        for(j = 0; j < i; ++j) {
                                (*fps[j].uninit)();
                        }
                        return -1;
                }
        }
        return 0;
}

int main() {
        init();
        return 0;
}

Вывод:

uninit_a()
uninit_b()

Это тот же порядок, в котором будет выполнен код в исходном сообщении, но вы можете его отменить (внутренний цикл).

Ответ 8

То, что вы, возможно, ищете, - это управление ресурсами, связанное с областью. С++ традиционно делает это с конструкторами/деструкторами. Но есть способ сделать это по-другому (как на C99, так и на С++), немного нарушив for -statement. Я написал что-то на этой строке здесь: управление ресурсами, связанное с областью видимости, для областей видимости.

Ответ 9

У меня нет компилятора, чтобы попробовать это. Но что-то вроде этого может работать?

int (*init[])() = {init_A, init_B, init_C, init_D, init_E};
int (*uninit[])() = {uninit_A, uninit_B, uninit_C, uninit_D, uninit_E};

int main()
{
  initfunction(init, 0)
  return 0;
}

void initfunction((*init[])(), pos)
{
  if(init[pos]() == TRUE)
    initfunction(init, pos++)
  else
    return;

  uninit[pos]();
}

Ответ 10

int X = 0;
if(init_A() == TRUE) {
  X++;
  if(init_B() == TRUE) {
    X++;
    if(init_C() == TRUE) {
      X++;
      if(init_D() == TRUE) {
        X++;
        if(init_E() == TRUE) {
          X++;
          /* ALL STARTED OK */    
        }
      }
    }
  }
}

/* You said reverse order which I took to mean this,
 * though your did not do it this way. */
switch (X) {
  case 5:
      return SUCCESS;
  case 4:
    uninit_D();
  case 3:
    uninit_C();
  case 2:
    uninit_B();
  case 1:
    uninit_A();
    return FAILURE;
}

Что-то, что я делаю, чтобы не допускать ошибок в коде, например:

static int do_A(void);
static int do_B(void);
static int do_C(void);
static int do_D(void);

static int do_A(void) {
   if (init_A() == FALSE) {
      return FALSE;
   }
   if (do_B() == FALSE) {
      uninit_A();
      return FALSE;
   }
   return TRUE;
}    

...

static int do_D(void) {
    return init_D();
}

Все остальные функции do_ должны выглядеть так же, как do_A.