Por que a instrução switch foi projetada para precisar de uma pausa?

139

Dada uma simples instrução switch

switch (int)
{
    case 1 :
    {
        printf("1\n");
        break;
    }

    case 2 : 
    {
        printf("2\n");
    }

    case 3 : 
    {
        printf("3\n");
    }
}

A ausência de uma declaração de interrupção no caso 2, implica que a execução continuará dentro do código do caso 3. Isso não é um acidente; foi projetado dessa maneira. Por que essas decisões foram tomadas? Que benefício isso oferece versus ter uma semântica de quebra automática para os blocos? Qual foi a justificativa?

EvilTeach
fonte

Respostas:

150

Muitas respostas parecem focar na capacidade de cair como o motivo para exigir a breakdeclaração.

Acredito que foi simplesmente um erro, devido em grande parte porque quando C foi projetado, não havia quase tanta experiência com a forma como essas construções seriam usadas.

Peter Van der Linden defende seu livro "Expert C Programming":

Analisamos as fontes do compilador Sun C para ver com que frequência a falha padrão foi usada. O front end do compilador Sun ANSI C possui 244 instruções de opção, cada uma com uma média de sete casos. A queda ocorre em apenas 3% de todos esses casos.

Em outras palavras, o comportamento normal da troca está errado 97% das vezes. Não é apenas em um compilador - pelo contrário, onde o fall through foi usado nessa análise foi geralmente para situações que ocorrem com mais frequência em um compilador do que em outro software, por exemplo, ao compilar operadores que podem ter um ou dois operandos :

switch (operator->num_of_operands) {
    case 2: process_operand( operator->operand_2);
              /* FALLTHRU */

    case 1: process_operand( operator->operand_1);
    break;
}

A queda de casos é tão amplamente reconhecida como um defeito que até existe uma convenção de comentário especial, mostrada acima, que diz ao fiapo "este é realmente um daqueles 3% de casos em que a queda foi desejada".

Eu acho que foi uma boa ideia para o C # exigir uma instrução de salto explícita no final de cada bloco de caso (enquanto ainda permite que vários rótulos de caso sejam empilhados - desde que haja apenas um único bloco de instruções). No C #, você ainda pode fazer com que um caso caia para outro - você só precisa tornar explícita a queda saltando para o próximo caso usando a goto.

É uma pena que o Java não tenha aproveitado a oportunidade para romper com a semântica C.

Michael Burr
fonte
Na verdade, acho que eles foram pela simplicidade da implementação. Algumas outras linguagens suportaram casos mais sofisticados (intervalos, vários valores, seqüências de caracteres ...) com o custo, talvez, de eficiência.
#
Java provavelmente não queria quebrar hábitos e espalhar confusão. Para um comportamento diferente, eles teriam que usar semânticas diferentes. Os designers de Java perderam várias oportunidades de romper com o C, de qualquer maneira.
PhiLho
@ Phiho - Eu acho que você provavelmente está mais próximo da verdade com "simplicidade de implementação".
227 Michael Burr
Se você estiver usando saltos explícitos via GOTO, não é tão produtivo usar apenas uma série de instruções IF?
DevinB 13/04
3
até Pascal implementa seu switch sem interrupção. como poderia o compilador-codificador C não pensar nisso @@
Chan Le
30

De várias maneiras, c é apenas uma interface limpa para idiomas de montagem padrão. Ao escrever o controle de fluxo acionado por tabela de salto, o programador tem a opção de cair ou saltar da "estrutura de controle", e um salto exige uma instrução explícita.

Então, c faz a mesma coisa ...

dmckee --- gatinho ex-moderador
fonte
1
Sei que muitas pessoas dizem isso, mas acho que não é o quadro completo; C também costuma ser uma interface feia para antipadrões de montagem. 1. Feio: Declarações em vez de expressões. "Declaração reflete uso." Glorioso cópia-colar como o mecanismo para a modularidade e portabilidade. Tipo de sistema de maneira muito complicado; incentiva bugs. NULL. (Continua no próximo comentário.)
Harrison
2. Antipadrões: não fornece uniões marcadas "limpas", um dos dois idiomas fundamentais de representação de dados da montagem. (Knuth, vol. 1, cap. 2) Em vez disso, "uniões não identificadas", um hack não-idiomático. Essa escolha prejudicou a maneira como as pessoas pensam nos dados por décadas. E NULseqüências de caracteres terminadas são a pior idéia de todas.
Harrison
@HarrisonKlaperman: Nenhum método de armazenamento de strings é perfeito. A grande maioria dos problemas associados a cadeias terminadas em nulo não ocorreriam se as rotinas que aceitassem cadeias também aceitassem um parâmetro de tamanho máximo e ocorreriam com rotinas que armazenam cadeias marcadas em comprimentos em buffers de tamanho fixo sem aceitar um parâmetro de tamanho de buffer . O design da instrução switch, onde os casos são simplesmente rótulos, pode parecer estranho aos olhos modernos, mas não é pior que o design do DOloop Fortran .
Supercat
Se eu decidir escrever uma tabela de salto na montagem, pegarei o valor do caso e, magicamente, o transformarei no subscrito da tabela de salto, pule para esse local e execute o código. Depois disso, não vou pular para o próximo caso. Vou pular para um endereço uniforme que é a saída de todos os casos. A ideia de que eu pularia ou cairia no corpo do próximo caso é uma tolice. Não há caso de uso para esse padrão.
21714 EvilTeach
2
Enquanto hoje em dia as pessoas estão mais acostumadas a limpar e autoproteger os recursos de linguagem e de linguagem que impedem que alguém atire no pé, isso é uma reminiscência de uma área onde bytes eram caros (C começou antes de 1970). se o seu código precisar caber em 1024 bytes, você terá um grande pesar ao reutilizar fragmentos de código. Reutilizar código iniciando em diferentes pontos de entrada que compartilham o mesmo fim é um mecanismo para conseguir isso.
RPY
23

Para implementar o dispositivo de Duff, obviamente:

dsend(to, from, count)
char *to, *from;
int count;
{
    int n = (count + 7) / 8;
    switch (count % 8) {
    case 0: do { *to = *from++;
    case 7:      *to = *from++;
    case 6:      *to = *from++;
    case 5:      *to = *from++;
    case 4:      *to = *from++;
    case 3:      *to = *from++;
    case 2:      *to = *from++;
    case 1:      *to = *from++;
               } while (--n > 0);
    }
}
FlySwat
fonte
3
Eu amo o dispositivo de Duff. Tão elegante e perverso rapidamente.
Dicroce 31/10/08
7
sim, mas ele precisa aparecer toda vez que há uma instrução switch no SO?
billjamesdev
Você perdeu duas chaves de fechamento ;-).
Toon Krijthe
18

Se os casos foram projetados para serem quebrados implicitamente, você não pode ter sucesso.

case 0:
case 1:
case 2:
    // all do the same thing.
    break;
case 3:
case 4:
    // do something different.
    break;
default:
    // something else entirely.

Se o switch foi projetado para ocorrer implicitamente após cada caso, você não teria escolha. A estrutura da caixa do switch foi projetada da maneira que deve ser mais flexível.

Bill the Lizard
fonte
12
Você pode imaginar uma opção que interrompa implicitamente, mas tem uma palavra-chave "avanço". Inábil, mas viável.
dmckee --- ex-moderador gatinho
Como isso seria melhor? Eu imagino que uma declaração de caso funcione dessa maneira com mais frequência do que "bloco de código por caso" ... isso é um bloco se / então / outra.
billjamesdev
Eu acrescentaria que, na pergunta, os anexos de escopo {} em cada bloco de caso estão aumentando a confusão, pois parece com o estilo de uma declaração "while".
Matthew Smith
1
@ Bill: Acho que seria pior, mas abordaria a queixa apresentada por Mike B: que o outono (embora não seja o caso múltiplo) é um evento raro e não deve ser o comportamento padrão.
dmckee --- ex-moderador gatinho
1
Eu deixei de fora os aparelhos por questões de brevidade, com várias declarações que deveriam estar lá. Concordo que a inovação deve ser usada apenas quando os casos são exatamente os mesmos. É confuso como o inferno quando os casos se baseiam em casos anteriores usando o avanço.
Bill o Lagarto
15

As instruções de caso em uma instrução switch são simplesmente rótulos.

Quando você ativa um valor, a instrução switch efetivamente vai para o rótulo com o valor correspondente.

Isso significa que a interrupção é necessária para evitar a passagem para o código no próximo rótulo.

Quanto ao motivo pelo qual foi implementado dessa maneira - a natureza de falha de uma instrução switch pode ser útil em alguns cenários. Por exemplo:

case optionA:
    // optionA needs to do its own thing, and also B's thing.
    // Fall-through to optionB afterwards.
    // Its behaviour is a superset of B's.
case optionB:
    // optionB needs to do its own thing
    // Its behaviour is a subset of A's.
    break;
case optionC:
    // optionC is quite independent so it does its own thing.
    break;
LeopardSkinPillBoxHat
fonte
2
Existem dois grandes problemas: 1) Esquecer breakonde é necessário. 2) Se as instruções do caso tiveram sua ordem alterada, a queda pode resultar na execução do caso errado. Portanto, acho o manuseio do C # muito melhor (explícito goto casepara falhas, exceto para rótulos de casos vazios).
Daniel Rose
@DanielRose: 1) existem maneiras de esquecer também breakem C # - a mais simples é quando você não quer casefazer nada, mas se esqueça de adicionar um break(talvez você tenha ficado tão envolvido no comentário explicativo ou tenha sido chamado por algum motivo) outra tarefa): a execução cairá apenas para o caseabaixo. 2) encorajar goto caseé encorajar a codificação "espaguete" não estruturada - você pode terminar com ciclos acidentais ( case A: ... goto case B; case B: ... ; goto case A;), especialmente quando os casos são separados no arquivo e difíceis de grok em combinação. No C ++, o fall-through é localizado.
Tony Delroy
8

Para permitir coisas como:

switch(foo) {
case 1:
    /* stuff for case 1 only */
    if (0) {
case 2:
    /* stuff for case 2 only */
    }
    /* stuff for cases 1 and 2 */
case 3:
    /* stuff for cases 1, 2, and 3 */
}

Pense na casepalavra - chave como um gotorótulo e ela será muito mais natural.

R .. GitHub PARE DE AJUDAR O GELO
fonte
8
Que if(0)no final do primeiro caso seja ruim, e só deve ser usado se o objetivo for ofuscar o código.
Daniel Rose
11
Toda essa resposta foi um exercício de ser mau, eu acho. :-)
R .. GitHub PARE DE AJUDAR ICE
3

Elimina a duplicação de código quando vários casos precisam executar o mesmo código (ou o mesmo código em sequência).

Como no nível da linguagem assembly, não importa se você quebra entre eles ou não existe zero de sobrecarga para os casos de qualquer maneira, por que não permitir isso, pois eles oferecem vantagens significativas em certos casos.

Greg Rogers
fonte
2

Por acaso, me deparei com um caso de atribuição de valores em vetores a estruturas: tinha que ser feito de tal maneira que se o vetor de dados fosse menor que o número de membros de dados na estrutura, o restante dos membros permaneceria em seu valor padrão. Nesse caso, omitir breakfoi bastante útil.

switch (nShorts)
{
case 4: frame.leadV1    = shortArray[3];
case 3: frame.leadIII   = shortArray[2];
case 2: frame.leadII    = shortArray[1];
case 1: frame.leadI     = shortArray[0]; break;
default: TS_ASSERT(false);
}
Martti K
fonte
0

Como muitos aqui especificaram, é para permitir que um único bloco de código funcione para vários casos. Isso deveria ser uma ocorrência mais comum para as instruções do switch do que o "bloco de código por caso" especificado no seu exemplo.

Se você tiver um bloco de código por caso sem falhas, talvez considere usar um bloco if-elseif-else, pois isso pareceria mais apropriado.

billjamesdev
fonte