Qual é o uso idiomático de blocos arbitrários em C?

15

Um bloco é uma lista de instruções a serem executadas. Exemplos de onde os blocos aparecem em C são após uma declaração while e em if

while( boolean expression)
    statement OR block

if (boolean expression)
    statement OR block

C também permite que um bloco seja aninhado em um bloco. Eu posso usar isso para reutilizar nomes de variáveis, suponha que eu realmente goste de 'x'

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

imprimirá o número 5 dez vezes. Acho que pude ver situações em que é desejável manter o número de nomes de variáveis ​​baixo. Talvez em expansão macro. No entanto, não vejo nenhum motivo difícil para precisar desse recurso. Alguém pode me ajudar a entender os usos desse recurso, fornecendo alguns idiomas onde ele é usado.

Jonathan Gallagher
fonte
1
Honestamente, estou apenas tentando entender a sintaxe C e estou curioso.
Jonathan Gallagher
O espírito de C é confiar no programador. O programador tem o poder de fazer algo grande ... ou terrível. Pessoalmente, não gosto de nomes de variáveis ​​sobrecarregados, mas outro programador pode. No final do dia, se estamos realizando o que devemos fazer com o mínimo de erros ... por que deveríamos discutir?
mexer Bits
1
Esse é o sentimento que recebo de C. Sou a favor da sintaxe que suporta diferentes estilos de codificação (desde que a semântica da linguagem esteja correta). É só que ... eu vi isso, e minha resposta imediata foi: eu poderia aplicar uma transformação de fonte a fonte renomeando todas as variáveis ​​em um bloco com nomes novos e nivelar o bloco completamente. Sempre que penso que posso me livrar de algo, presumo que haja algo que perdi.
Jonathan Gallagher
Momento engraçado, como programador que não é C, deparei-me hoje com esta sintaxe no código C e fiquei curioso para o que era. Estou feliz que você perguntou.
Brandon

Respostas:

8

A idéia não é manter o número de nomes de variáveis ​​baixo ou incentivar a reutilização de nomes, mas limitar o escopo das variáveis. Se você tem:

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

em seguida, o escopo de yse limita ao bloco, o que significa que você pode esquecê-lo antes ou depois do bloco. Você vê isso usado com mais frequência em conexão com loops e condicionais. Você também vê isso com mais frequência em linguagens do tipo C, como C ++, onde uma variável que sai do escopo causa sua destruição.

Caleb
fonte
Eu acho que o bloco ocorre naturalmente com condicionais, loops e corpos funcionais. O que estou curioso é que, em C, posso colocar um bloco em qualquer lugar. Seu segundo comentário sobre C ++ é interessante - deixar um escopo causa destruição. Isso se refere apenas à coleta de lixo ou é outro uso a seguir: eu poderia pegar um corpo de função, que possui "seções" distintas e usar blocos para controlar o espaço ocupado pela memória, provocando a destruição do espaço variável?
Jonathan Gallagher
1
Tanto quanto sei, C pré-alocará toda a memória para todas as variáveis ​​na função. Tê-los a maneira da parte âmbito saída através de uma função não terá benefícios de desempenho em C. Da mesma forma tendo variáveis entram escopo "só às vezes" não vai mudar o consumo de memória durante qualquer chamada para a função
Gankro
@Gankro Pode ter um impacto se houver vários escopos aninhados exclusivos. Um compilador pode reutilizar um pedaço de memória pré-alocado exclusivo para variáveis ​​em cada um desses escopos. Obviamente, a razão pela qual isso não vem à mente é que, se um único escopo arbitrário já é um sinal, você provavelmente precisará extrair uma função, dois ou mais escopos arbitrários são definitivamente uma boa indicação de que você precisa refatorar. Ainda assim, ele aparece como uma solução sadia em coisas como bloqueios de caixas de comutação de tempos em tempos.
tne
16

Porque antigamente, em C, novas variáveis ​​só podiam ser declaradas em um novo bloco.

Dessa forma, os programadores poderiam introduzir novas variáveis ​​no meio de uma função sem vazá-la e minimizar o uso da pilha.

Nos otimizadores de hoje, é inútil e um sinal de que você precisa considerar a extração do bloco em sua própria função.

Em uma instrução switch, é útil colocar os casos em seus próprios blocos para evitar a declaração dupla.

No C ++, é muito útil, por exemplo, para os protetores de bloqueio RAII e garantir que os destruidores executem o bloqueio de liberação quando a execução estiver fora do escopo e ainda fizer outras coisas fora da seção crítica.

catraca arrepiante
fonte
2
+1 para os guardas de bloqueio RAII. Dito isto, esse mesmo conceito não poderia ser útil também para a desalocação de buffer de pilha grande em partes de uma rotina? Eu nunca fiz isso, mas soa como algo que definitivamente poderia ocorrer em algum código embutido ...
J Trana
2

Eu não consideraria isso blocos "arbitrários". Não é um recurso destinado tanto ao uso do desenvolvedor, mas a maneira como C usa blocos permite que a mesma construção seja usada em vários locais com a mesma semântica. Um bloco (em C) é um novo escopo e as variáveis ​​que o deixam são eliminadas. Isso é uniforme, independentemente de como o bloco é usado.

Em outros idiomas, esse não é o caso. Isso tem a vantagem de permitir menos abusos, como você mostra, mas a desvantagem que os blocos se comportam de maneira diferente, dependendo do contexto em que estão.

Eu raramente vi blocos autônomos usados ​​em C ou C ++ - geralmente quando há uma grande estrutura ou um objeto que representa uma conexão ou algo que você deseja forçar a destruição. Geralmente, essa é uma dica de que sua função está fazendo muitas coisas e / ou é muito longa.

Telastyn
fonte
1
Infelizmente, vejo regularmente blocos autônomos - em quase todos os casos, porque a função está fazendo muito e / ou é muito longa.
mattnz
Também vejo e escrevo regularmente blocos autônomos em C ++, mas apenas para forçar a destruição de wrappers em torno de bloqueios e outros recursos compartilhados.
J Trana
2

Você precisa entender que os princípios de programação que parecem óbvios agora nem sempre eram assim. As melhores práticas de C dependem muito da idade dos seus exemplos. Quando C foi introduzido pela primeira vez, dividir seu código em pequenas funções foi considerado muito ineficiente. Dennis Ritchie basicamente mentiu e disse que as chamadas de função eram realmente eficientes em C (não eram na época), que foi o que levou as pessoas a usá-las mais, embora os programadores de C de alguma forma nunca tenham passado completamente por uma cultura de otimização prematura.

ele é uma boa prática de programação ainda hoje para limitar o escopo de suas variáveis tão pequeno quanto possível. Atualmente, normalmente fazemos isso criando uma nova função, mas se as funções são consideradas caras, a introdução de um novo bloco é uma maneira lógica de limitar seu escopo sem a sobrecarga de uma chamada de função.

No entanto, eu comecei a programar em C há mais de 20 anos, quando você tinha que declarar todas as suas variáveis ​​no topo de um escopo, e não me lembro de sombreamento de variáveis ​​como se alguma vez fosse considerado um bom estilo. Redeclarando em dois blocos, um após o outro, como em uma instrução switch, sim, mas não na sombra. Talvez se a variável já foi usado e o nome específico foi altamente idiomática para a API que você está chamando, como deste srcem strcpy, por exemplo.

Karl Bielefeldt
fonte
1

Blocos arbitrários são úteis para introduzir variáveis ​​intermediárias que são usadas apenas em casos especiais de um cálculo.

Este é um padrão comum na computação científica, onde os procedimentos numéricos geralmente:

  1. confie em muitos parâmetros ou quantidades intermediárias;
  2. tem que lidar com muitos casos especiais.

Por causa do segundo ponto, é útil introduzir variáveis ​​temporárias de escopo limitado, o que é obtido com o uso de um bloco arbitrário ou com a introdução de uma função auxiliar.

Embora a introdução de uma função auxiliar possa parecer um acéfalo ou uma prática recomendada a seguir cegamente, na verdade existem poucos benefícios em fazê-lo nessa situação específica.

Como existem muitos parâmetros e quantidades intermediárias, queremos introduzir uma estrutura para transmiti-los à função auxiliar.

Mas, como queremos ser conseqüentes com nossas práticas, não apresentaremos apenas uma função auxiliar, mas várias. Portanto, se introduzimos estruturas ad-hoc que transmitem parâmetros para cada função, que introduzem muitas sobrecargas de código para mover parâmetros para frente e para trás, ou introduzimos uma que governará toda a estrutura da planilha, que contém todas as nossas variáveis, mas parece um pacote de bits sem consistência, onde a qualquer momento apenas a metade dos parâmetros tem um significado interessante.

Portanto, essas estruturas auxiliares são tipicamente pesadas e usá-las significa escolher entre inchar o código ou introduzir uma abstração cujo escopo é muito amplo e enfraquece o significado do programa, em vez de fortalecê-lo .

A introdução de funções auxiliares pode facilitar o teste de unidade do programa, introduzindo uma granularidade de teste mais fina, mas combinando o teste de unidade para procedimentos não de baixo nível e testes de regressão na forma de comparações (com número) de traços numéricos dos procedimentos, um trabalho igualmente bom .

user40989
fonte
0

Em geral, a reutilização de nomes de variáveis ​​dessa maneira causa muita confusão para futuros leitores do seu código. É melhor simplesmente nomear a variável interna de outra forma. De fato, a linguagem C # proíbe especificamente o uso de variáveis ​​dessa maneira.

Pude ver como o uso de blocos aninhados em expansões de macro seria útil para evitar colisões de nomes variáveis.

Robert Harvey
fonte
0

É para o escopo de coisas que normalmente não têm escopo nesse nível. Isso é extremamente raro e, em geral, a refatoração é uma escolha melhor, mas eu já o encontrei uma ou duas vezes no passado em declarações de opção:

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

Quando você considera a manutenção, a refatoração deve ser equilibrada com todas as implementações seguidas, desde que cada uma tenha apenas algumas linhas. Pode ser mais fácil tê-los todos juntos, em vez de uma lista de nomes de funções.

No meu caso, se bem me lembro, a maioria dos casos eram pequenas variações do mesmo código - exceto que um era idêntico ao outro, apenas com pré-processamento extra. Uma opção acabou sendo a forma mais simples para o código, e o escopo extra permitiu o uso de variáveis ​​locais extras com as quais ele e outros casos não precisaram se preocupar (como nomes de variáveis ​​que se sobrepõem acidentalmente a uma definida antes da opção, mas usado mais tarde).

Observe que a instrução switch é simplesmente o uso que eu encontrei. Tenho certeza de que isso também pode ser aplicado em outros lugares, mas não me lembro de vê-los usados ​​dessa maneira.

Izkata
fonte