Постарайтесь не копировать код

скажем, я имею:

  switch( choice ) {
  case A:   
     stmt;
     do_stmt_related2A;
  break;

  case B:
     stmt;
     do_stmt_related2B;
  break;

  case C: something_different();
   ...
  }

Как я мог постараться не копировать код stmt?

Но есть ли какое-либо обходное решение? маркировка расширения gcc как значение выглядит довольно хорошей для такой ситуации.

   switch( choice ) {
     do {
     case A:  ptr = &&A_label;
     break;
     case B:  ptr = &&B_label;
     } while(0);
              stmt;
              goto *ptr;
     case C: ...

Есть ли какой-либо прием, который может сделать то же в ANSI-C?Править: Конечно, я думал функциональный/макро-/встроенный. Но что-либо еще? Это не о производительности также. Только для образовательной цели.;)

9
задан Kate Gregory 29 June 2010 в 19:59
поделиться

14 ответов

Почему бы вам просто не реорганизовать stmt (я предполагаю, что это большой кусок инструкций, а не одна строка) в его собственную функцию do_stmt () и называть это? Что-то вроде:

switch( choice ) {
    case A:
        do_stmt();
        do_stmt_related2A;
        break;
    case B:
        do_stmt();
        do_stmt_related2B;
        break;
    case C: something_different();
        ...
}

Этот трюк с gcc действительно ужасен. Я бы предпочел иметь читаемый код вместо таких чудовищ в любой день.

Вы всегда должны предполагать, что программист, унаследовавший ваш код, - маньяк-убийца, который знает, где вы живете: -)

29
ответ дан 4 December 2019 в 05:54
поделиться

В любом случае будет какой-то код - вы можете иметь дублированный код или код, позволяющий избежать дублирования. Поэтому мне интересно, насколько сложным на самом деле является код stmt;.

Простое, чистое решение - переместить разделяемую часть (stmt) в отдельную функцию.

void do_shared_stmt(void) {
 stmt;
}
/* .... */
swtich(choise) {
case A:
  do_shared_stmt();
  do_stmt_related2A();
  break;
case B:
  do_shared_stmt();
  do_stmt_related2B();
  break;
case C:
  something_different();
/* ... */
}

Другое решение (которое может быть приемлемым, в зависимости от вашей ситуации) - вложение операторов ветвления:

swtich(choise) {
case A:
case B:
  stmt;
  if(choise == A) {
    do_stmt_related2A();
  } else {̈́
    do_stmt_related2B();
  }
  break;
case C:
  something_different();
/* ... */
}
4
ответ дан 4 December 2019 в 05:54
поделиться

Как мне избежать дублирования кода stmt?

Поместив его в функцию и вызвав ее.

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

17
ответ дан 4 December 2019 в 05:54
поделиться

Я бы выбрал что-то вроде того, что я приложил здесь. Конечно, вы, вероятно, сами придумали вложенный оператор switch, ничего нового. Что это делает дополнительно, так это то, что оценивает choice только один раз.

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

Заметим также, что my_choice является intmax_t, поэтому он должен быть совместим с любым типом, который может иметь choice.

(Вставка for - это просто ради интереса, и будет работать только в C99, очевидно. Вы можете заменить его дополнительным {} вокруг всего материала и просто объявлением my_choice для C89.)

typedef enum { one = 1, two, three } number;

int main(int argc, char** argv) {

  number choice = (number)argc;

  for (intmax_t _0 = 0, my_choice = choice; !_0; ++_0)
    switch(my_choice) {
    case one:;
    case two:;
      {
        printf("Do whatever you want here\n");
        switch (my_choice) {
        case one:
          printf("test 1\n");
          break;
        case two:
          printf("test 2\n");
          break;
        }
        printf("end special\n");
      }
      break;
    default:;
      printf("test %d, default\n", choice);
    }
}
0
ответ дан 4 December 2019 в 05:54
поделиться

Вам просто нужны две управляющие структуры. Один для указания выполнения инструкций первого порядка, второй для инструкций второго порядка.

switch (state) {
    case A:
    case B:
        stmt;

        switch (state) {
            case A:
                do_stmt_related2A;
                break;
            case B:
                do_stmt_related2B;
                break;
        }

        break;

    case C:
        something_different();
        ...
}

Также стоит отметить, что переключатель менее подходит для структуры управления второго порядка, вы, вероятно, захотите использовать более традиционный условный переход. Наконец, реальный ответ на ваш вопрос относительно goto-label-pointer заключается в том, что для этого и предназначена привязка подпрограмм. Если ваши инструкции сложнее , чем одно выражение, то вы можете и должны использовать функцию.

0
ответ дан 4 December 2019 в 05:54
поделиться

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

if (choice == A || choice == B) {
    stmt;
}

switch( choice ) {
  case A:   
    do_stmt_related2A;
    break;

  case B:
    do_stmt_related2B;
    break;

  case C:
    something_different();
    ...
}

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

9
ответ дан 4 December 2019 в 05:54
поделиться

Я, наверное, сделаю что-нибудь вроде этого:

void do_stmt(int choice)
{
    stmt;
    switch(choice)
    {
         case A:
             do_stmt_related2A;
             break;
         case B:
             do_stmt_related2B;
             break;
    }  
}
/* ... */
switch(choice)
{
    case A:
    case B:
        do_stmt(choice);
        break;
    case C:
         something_different();
...
1
ответ дан 4 December 2019 в 05:54
поделиться

Вот одна альтернатива вызовам функций или вторичным операторам переключения:

isA=false;
switch( choice ) { 
  case A:    
    isA=true;
  //nobreak
  case B: 
    stmt; 
    isA ? do_stmt_related2A : do_stmt_related2B;
  break; 
  case C: 
    something_different(); 
  break;
} 

Однако я не могу сказать, что действительно рекомендую его как стиль кодирования.

0
ответ дан 4 December 2019 в 05:54
поделиться

Вернитесь к первоначальным значениям различных падежных окончаний. Что представляют собой случаи A, B и C? Если A и B должны выполнять частично один и тот же код, у них должны быть некоторые сходства. Найдите их и выразите другим способом.

Следующий пример делает это более понятным. Предположим, что A, B и C - это 3 разных вида животных, а дублирующийся код для A и B на самом деле типичен для животных, которые могут летать. Тогда вы можете написать свой код следующим образом:

if (can_fly(choice))
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }

Таким образом, если позже в ваше приложение будет добавлен третий вариант D, вероятность забыть дублированный код "stmt" будет немного меньше.

Если вы действительно хотите предотвратить вызов функции (to can_fly), а A, B и C - числовые константы, можно воспользоваться битмасками. В этом примере мы используем один бит, чтобы указать, что животное может летать:

if (choice & BIT_CAN_FLY)
   {
   stmt;
   }

switch( choice )
  {
  case A:   
     do_stmt_related2A;
     break;
  case B:
     do_stmt_related2B;
     break;
  case C:
     something_different();
     break;
  }
0
ответ дан 4 December 2019 в 05:54
поделиться

Использование указателей goto, вероятно, приведет к замедлению кода, потому что он отключает некоторые другие оптимизации gcc (или это произойдет в последний раз, когда я читал об этом). Gcc, по сути, решает, что было бы слишком сложно угнаться за тем, что может происходить, и предполагает, что гораздо больше инструкций ветвления могут нацеливаться на каждую метку goto, которая была &&, чем на самом деле. Если вы настаиваете на использовании этого метода, я предлагаю вам попытаться использовать целое число и другой переключатель / регистр, а не goto. Gcc сможет понять это.

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

Рефакторинг stmt в статическую функцию, вероятно, даст хорошие результаты, если stmt действительно дорогой или большой код.

Еще одна вещь, которую вы могли бы попробовать, - это вытащить stmt из переключателя / корпуса и просто выполнить, всегда выполнять его. Иногда это самый дешевый способ, но это действительно зависит от того, что на самом деле делает stmt .

Еще вы можете выполнить рефакторинг всего stmt , do_stmt_related2A , и do_stmt_related2A все в файловых статических функциях, например:

// args in this is just a psudocode place holder for whatever arguments are needed, and 
// not valide C code.
static void stmt_f(void (*continuation)(arg_list), args) {
   stmt; // This corresponds almost exactly to stmt in your code
   continuation(args);
}
static void do_stmt_related2A_f(args) {
    do_stmt_related2A;
}
static void do_stmp_related2B_f(args) {
    do_stmt_related2B;
}

...
    switch (condition) {
       case A:
          stmt_f(do_stmt_related2A_f, args);
          break;
       case B:
    ...

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

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

И последнее, что вы можете попробовать, - это контролировать, какие фактические значения A, B, C ... могут принимать, тогда вы можете убедиться, что значения с похожими префиксами имеют смежные значения. Если A и B действительно находятся рядом друг с другом, и если компилятор решит, что ему нужно разбить переключатель / регистр на несколько разных таблиц переходов, он, вероятно, поместит A и B в одну таблицу переходов, а также увидит, что у них есть тот же префикс и поднимите этот код для вас. Это более вероятно, если C, у которого нет этого префикса, не находится рядом с A или B, но тогда ваш общий код может быть хуже.

0
ответ дан 4 December 2019 в 05:54
поделиться

Другие уже дали этот ответ, но я хотел изложить его в формальных терминах.

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

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

0
ответ дан 4 December 2019 в 05:54
поделиться

Я вижу только два возможных варианта вашей логики.

L1:
 val
 |
 |-------------------
 |        |         |
 A        B         C
 |        |         |
 stmt     stmt      crap
 |        |
 Afunk    Bfunk

L2:
 val
 |
 |-------------------
 stmt               |
 |---------         |
 |        |         |
 A        B         C
 |        |         |
 Afunk    Bfunk     crap

В L1 используйте встроенную функцию для stmt или используйте L2, если двойное ветвление в порядке.Если вы говорите о дублировании кода src (в отличие от дублирования в двоичном файле), просто встроенная функция решит вашу проблему.

0
ответ дан 4 December 2019 в 05:54
поделиться

Если вы используете gcc, один из вариантов - вложенные функции . Это функции, которые имеют доступ ко всем переменным родительской функции.

Например:

void foo(int bar) {

    int x = 0;
    void stmt(void) { //nested function!!
        x++;
        if (x == 8) {
            x = 0;
        }
    }

    switch( bar ) {
    case A:   
        stmt();
        do_stmt_related2A;
    break;

    case B:
        stmt();
        do_stmt_related2B;
    break;

    case C:
        something_different();
        ...
    break;
    }
}

Поскольку это расширение gcc, его, очевидно, не следует использовать для кода, предназначенного для переносимости. Но с ним может быть весело :) и в определенных обстоятельствах код становится более читабельным.

Если у вас есть исполняемый стек, вы даже можете создать указатель на вложенную функцию. Это очень похоже на лямбда-функцию, только ужасно - или ужасно забавно, в зависимости от вашей точки зрения. (Как и все указатели на «локальные» объекты, указатель становится недействительным при выходе из родительской функции! Осторожно!)

0
ответ дан 4 December 2019 в 05:54
поделиться

Вы можете просто поместить ваш stmt код в функцию и вызвать эту функцию из вашего кейса или откуда угодно, вы можете вызывать функцию сколько угодно раз.

0
ответ дан 4 December 2019 в 05:54
поделиться
Другие вопросы по тегам:

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