Что такое идиоматическое использование произвольных блоков в C?

15

Блок - это список операторов, которые должны быть выполнены. Примеры того, где блоки появляются в C, - через оператор while и оператор if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C также позволяет блоку быть вложенным в блок. Я могу использовать это, чтобы повторно использовать имена переменных, предположим, что мне действительно нравится 'x'

int x = 0;
while (x < 10)
{
    {
        int x = 5;
        printf("%d",x)
    }
    x = x+1;
}

напечатает номер 5 десять раз. Я думаю, я мог видеть ситуации, когда желательно, чтобы количество имен переменных было низким. Возможно в расширении макроса. Тем не менее, я не вижу какой-либо веской причины для необходимости этой функции. Может ли кто-нибудь помочь мне понять использование этой функции, предоставив некоторые идиомы, где она используется.

Джонатан Галлахер
источник
1
Честно говоря, я просто пытаюсь понять синтаксис Си, и мне любопытно.
Джонатан Галлахер
Дух C заключается в доверии к программисту. Программист имеет право сделать что-то великое ... или сделать что-то ужасное. Лично мне не нравятся перегруженные имена переменных, но другой программист может. В конце концов, если мы выполняем то, что мы должны с минимальными ошибками ... почему мы должны спорить?
Fitdling Bits
1
Это чувство, которое я получаю от C. Я полностью за синтаксис, который поддерживает разные стили кодирования (ну, пока семантика языка в порядке). Это просто ... Я видел это, и мой немедленный ответ был таким: я мог применить преобразование источник-источник, переименовав все переменные в блоке со свежими именами, и полностью сгладить блок. Каждый раз, когда я думаю, что могу от чего-то избавиться, я предполагаю, что что-то упустил.
Джонатан Галлахер
Забавно, что как программист не на C я сегодня наткнулся на этот синтаксис в C-коде и мне было любопытно, для чего он нужен. Я рад, что ты спросил.
Брэндон

Ответы:

8

Идея состоит не в том, чтобы поддерживать малое количество имен переменных или иным образом поощрять повторное использование имен, а в том, чтобы ограничить область видимости переменных. Если у вас есть:

int x = 0;
//...
{
    int y = 3;
    //...
}
//...

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

Калеб
источник
Я думаю, что блок естественно возникает с условными, циклическими и функциональными телами. Что мне интересно, так это то, что в C я могу разместить блок где угодно. Ваш второй комментарий о C ++ интересен - что выход из области действия приводит к разрушению. Относится ли это только к сборке мусора, или это другое использование: могу ли я взять тело функции, которое имеет отдельные «секции», и использовать блоки для управления объемом памяти, инициируя уничтожение пространства переменных?
Джонатан Галлахер
1
Насколько я знаю, C будет предварительно выделять всю память для всех переменных в функции. Если они выходят из области видимости через часть функции, это не даст никаких преимуществ с точки
зрения
@Gankro Это может оказать влияние, если есть несколько эксклюзивных вложенных областей. Компилятор может повторно использовать уникальный предварительно выделенный кусок памяти для переменных в каждой из этих областей. Конечно, причина, по которой это не приходит в голову, состоит в том, что если одна произвольная область видимости уже является признаком, который вам, вероятно, нужно извлечь в функцию, две или более произвольные области видимости, безусловно, являются хорошим показателем того, что вам необходимо провести рефакторинг. Тем не менее, он время от времени появляется как нормальное решение таких вещей, как переключение блоков.
TNE
16

Потому что в старые времена C новые переменные могли быть объявлены только в новом блоке.

Таким образом, программисты могут вводить новые переменные в середине функции, не пропуская ее и сводя к минимуму использование стека.

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

В операторе switch полезно заключать регистры в свои собственные блоки, чтобы избежать двойного объявления.

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

чокнутый урод
источник
2
+1 для охранников замка RAII. При этом, разве эта концепция не может быть полезной для освобождения буфера стека большого размера внутри частей процедуры? Я никогда не делал этого, но звучит так, как будто это может произойти в каком-то встроенном коде ...
J Trana
2

Я не смотрю на это как на «произвольные» блоки. Это не особенность, предназначенная для разработчиков, но способ, которым C использует блоки, позволяет использовать одну и ту же конструкцию блока во многих местах с одинаковой семантикой. Блок (в C) - это новая область, и переменные, которые покидают его, исключаются. Это единообразно независимо от того, как используется блок.

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

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

Telastyn
источник
1
К сожалению, я регулярно вижу отдельные блоки - почти во всех случаях, потому что функция делает много и / или слишком долго.
Mattnz
Я также регулярно вижу и пишу автономные блоки на C ++, но исключительно для принудительного уничтожения оболочек вокруг блокировок и других общих ресурсов.
J Trana
2

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

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

Тем не менее, я начал программировать на C более 20 лет назад, когда вам приходилось объявлять все переменные в верхней части области, и я не помню, чтобы теневое копирование переменных считалось хорошим стилем. Перераспределение в два блока один за другим, как в операторе switch, да, но не дублирование. Возможно, если переменная уже использовалась, и конкретное имя было очень идиоматичным для API, который вы вызываете, например, destи srcв strcpy, например.

Карл Билефельдт
источник
1

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

Это распространенная модель в научных вычислениях, где числовые процедуры обычно:

  1. полагаться на множество параметров или промежуточных величин;
  2. приходится иметь дело с множеством особых случаев.

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

Хотя введение вспомогательной функции может показаться легким делом или лучшей практикой слепого следования, на самом деле в этой конкретной ситуации это мало что дает .

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

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

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

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

user40989
источник
0

Как правило, повторное использование имен переменных вызывает слишком много путаницы для будущих читателей вашего кода. Лучше просто назвать внутреннюю переменную как-нибудь еще. Фактически, язык C # специально запрещает использование переменных таким способом.

Я мог видеть, как использование вложенных блоков в расширениях макросов было бы полезно для предотвращения конфликтов имен переменных.

Роберт Харви
источник
0

Это для областей видимости, которые обычно не имеют возможности на этом уровне. Это крайне редко, и в целом рефакторинг - лучший выбор, но в прошлом я сталкивался с ним один или два раза в выражениях switch:

switch(foo) {
   case 1:
      {
         // bar
      }
   case 2:
   case 3:
      // baz
      break;
   case 4:
   case 5:
      // bang
      break;
}

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

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

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

Izkata
источник