O que há de errado com este código C de 1988?

94

Estou tentando compilar este pedaço de código do livro "The C Programming Language" (K & R). É uma versão básica do programa UNIXwc :

#include <stdio.h>

#define IN   1;     /* inside a word */
#define OUT  0;     /* outside a word */

/* count lines, words and characters in input */
main()
{
    int c, nl, nw, nc, state;

    state = OUT;
    nl = nw = nc = 0;
    while ((c = getchar()) != EOF) {
        ++nc;
        if (c == '\n')
            ++nl;
        if (c == ' ' || c == '\n' || c == '\t')
            state = OUT;
        else if (state == OUT) {
            state = IN;
            ++nw;
        }
    }
    printf("%d %d %d\n", nl, nw, nc);
}

E estou recebendo o seguinte erro:

$ gcc wc.c 
wc.c: In function main’:
wc.c:18: error: else without a previous if
wc.c:18: error: expected ‘)’ before ‘;’ token

A 2ª edição deste livro é de 1988 e sou muito novo em C. Talvez tenha a ver com a versão do compilador ou talvez eu esteja apenas falando bobagem.

Eu vi no código C moderno um uso diferente da mainfunção:

int main()
{
    /* code */
    return 0;
}

Este é um novo padrão ou ainda posso usar um principal sem tipo?

César
fonte
4
Não é uma resposta, mas outro código para examinar mais de perto || c = '\t'). Parece igual ao outro código dessa linha?
user7116
58
32 votos positivos para uma questão de depuração + erro de digitação ?!
Lightness Races in Orbit
37
@ TomalakGeret'kal: você sabe, coisas antigas valem mais (vinho, pinturas, código C)
Sergio Tulentsev
16
@ César: Tenho todo o direito de expressar minha opinião e agradeço se não tentar censurá-la. Acontece que sim, este não é um site para depurar seu código e resolver seus erros tipográficos, que são problemas "localizados" que nunca ajudarão ninguém. É um site para perguntas sobre linguagens de programação , não para fazer depuração básica e trabalho de referência para você. O nível de habilidade é completamente irrelevante. Leia o FAQ, e talvez também esta meta pergunta .
Lightness Races in Orbit
11
@ TomalakGeret'kal, é claro que você pode expressar sua opinião e não vou censurar seu comentário, apesar de não ser construtivo. Já li o FAQ. Sou um programador entusiasta perguntando sobre um problema real que estou enfrentando
César

Respostas:

247

Seu problema é com as definições do pré-processador de INe OUT:

#define IN   1;     /* inside a word */
#define OUT  0;     /* outside a word */

Observe como você tem um ponto e vírgula final em cada um deles. Quando o pré-processador os expandir, seu código se parecerá com:

    if (c == ' ' || c == '\n' || c == '\t')
        state = 0;; /* <--PROBLEM #1 */
    else if (state == 0;) { /* <--PROBLEM #2 */
        state = 1;;

Esse segundo ponto-e-vírgula faz elsecom que o não tenha anterior ifcomo uma correspondência, porque você não está usando colchetes. Portanto, remova os pontos-e-vírgulas das definições do pré-processador de INe OUT.

A lição aprendida aqui é que as instruções do pré-processador não precisam terminar com um ponto-e-vírgula.

Além disso, você deve sempre usar aparelho!

    if (c == ' ' || c == '\n' || c == '\t') {
        state = OUT;
    } else if (state == OUT) {
        state = IN;
        ++nw;
    }

Não há elseambigüidade suspensa no código acima.

user7116
fonte
8
Para maior clareza, o problema não é o espaçamento, são os pontos e vírgulas. Você não precisa deles nas instruções do pré-processador.
Dan
@Dan obrigado pelo esclarecimento! E os pontos-e-vírgulas eram de fato o problema! Obrigado rapazes!
César
2
@ César: de nada. A sugestão estimulante irá mantê-lo longe de problemas no futuro, certamente me ajudou!
user7116
5
@ César: Também é uma boa ideia se acostumar a colocar as macros entre parênteses, pois geralmente você deseja que a macro seja avaliada primeiro. Nesse caso, não importa, pois o valor é um único token, mas deixar de fora os parênteses pode levar a resultados inesperados ao definir uma expressão.
styfle
7
"não precisa deles"! = "não deveria tê-los". o primeiro é sempre verdadeiro; o último depende do contexto e é a questão mais pertinente neste cenário.
Lightness Races in Orbit
63

O principal problema com este código é que ele é não o código da K & R. Inclui ponto e vírgula após as definições de macros, que não estavam presentes no livro, que, como outros apontaram, muda o significado.

Exceto ao fazer uma alteração na tentativa de entender o código, você deve deixá-lo sozinho até entendê-lo. Você só pode modificar com segurança o código que você entende.

Provavelmente foi apenas um erro de digitação de sua parte, mas ilustra a necessidade de compreensão e atenção aos detalhes durante a programação.

Jmoreno
fonte
9
Seu conselho não é muito construtivo para alguém que está aprendendo a programar. Modificar o código é precisamente como você entende os detalhes da programação.
user7116
12
@sixlettervariables: E ao fazer isso, você deve saber quais alterações você fez e fazer o mínimo possível. Se o OP tivesse feito as alterações deliberadamente, e feito o mínimo possível, ele provavelmente não teria feito essa pergunta, pois teria ficado claro para ele o que estava acontecendo. Ele teria mudado a macro para IN, sem erros e depois a macro para OUT com os dois erros, o segundo dos quais estaria reclamando do ponto-e-vírgula que ele acabou de adicionar.
jmoreno
5
Parece que, a menos que você cometa o erro de incluir um ponto-e-vírgula no final de uma linha de diretiva do pré-processador, provavelmente não saberá que não deve incluí-los. Você poderia interpretar pelo valor de face, poderia ler muito código e perceber que eles nunca parecem estar lá. Ou o OP pode bagunçar ao incluí-los, perguntar sobre o erro "bizarro" e descobrir: oops, nenhum ponto-e-vírgula necessário para diretivas de pré-processador! Isso é programação, não um episódio de Scared Straight.
user7116
14
@sixlettervariables: Sim, mas quando o código não funciona, o primeiro passo óbvio é dizer "ah, ok, então o que eu mudei sem qualquer razão a partir do código escrito em um livro do inventor de C, foi provavelmente o problema. Vou desfazer isso então. "
Lightness Races in Orbit
34

Não deve haver ponto-e-vírgula após as macros,

#define IN   1     /* inside a word */
#define OUT  0     /* outside a word */

e provavelmente deveria ser

if (c == ' ' || c == '\n' || c == '\t')
onemach
fonte
Obrigado, os pontos-e-vírgulas eram o problema. O segundo foi um erro de digitação!
César
21
Da próxima vez, cole o código exato que você usa, diretamente do seu editor de texto.
Lightness Races in Orbit
@ TomalakGeret'kal bem eu não fiz e irei, mas como você achou?
onemach
1
@onemach: Você disse que ;era um erro de digitação que não afetou o problema, o que significa um erro de digitação na sua pergunta, e não no código que você realmente usou.
Lightness Races in Orbit
24

As definições de IN e OUT devem ser assim:

#define IN   1     /* inside a word  */
#define OUT  0     /* outside a word */

Os pontos-e-vírgulas estavam causando o problema! A explicação é simples: tanto IN quanto OUT são diretivas de pré-processador, essencialmente o compilador irá substituir todas as ocorrências de IN por 1 e todas as ocorrências de OUT por 0 no código-fonte.

Como o código original tinha um ponto-e-vírgula após o 1 e o 0, quando IN e OUT foram substituídos no código, o ponto-e-vírgula extra após o número produziu um código inválido, por exemplo esta linha:

else if (state == OUT)

Acabou ficando assim:

else if (state == 0;)

Mas o que você queria era isso:

else if (state == 0)

Solução: remova o ponto-e-vírgula após os números na definição original.

Óscar López
fonte
8

Como você pode ver, houve um problema nas macros.

O GCC tem a opção de parar após o pré-processamento. (-E) Esta opção é útil para ver o resultado do pré-processamento. Na verdade, a técnica é importante se você estiver trabalhando com uma grande base de código em c / c ++. Normalmente, os makefiles terão um destino para parar após o pré-processamento.

Para referência rápida: A questão do SO cobre as opções - Como vejo um arquivo de origem C / C ++ após o pré-processamento no Visual Studio? . Ele começa com vc ++, mas também tem opções gcc mencionadas abaixo .

Jayan
fonte
7

Não é exatamente um problema, mas a declaração de main()também está datada, deveria ser algo assim.

int main(int argc, char** argv) {
    ...
    return 0;
}

O compilador assumirá um valor de retorno interno para uma função sem uma, e tenho certeza que o compilador / vinculador irá contornar a falta de declaração para argc / argv e a falta de valor de retorno, mas eles deveriam estar lá.

Conta
fonte
3
É um bom livro - um dos dois únicos livros que valem a pena sobre C, pelo que eu sei. Tenho certeza de que as edições mais recentes são compatíveis com ANSI C (provavelmente antes do C99 ANSI C). O outro livro que vale a pena sobre C é Expert C Programming Deep C Secrets, de Peter van der Linden.
Bill
Eu nunca disse que era. Eu simplesmente fui comentado que, para alinhar com a forma como as coisas são feitas hoje, esse principal deveria ser alterado.
Bill
4

Tente adicionar chaves explícitas ao redor dos blocos de código. O estilo K&R pode ser ambíguo.

Observe a linha 18. O compilador está informando onde está o problema.

    if (c == '\n') {
        ++nl;
    }
    if (c == ' ' || c == '\n' || c == '\t') { // You're missing an "=" here; should be "=="
        state = OUT;
    }
    else if (state == OUT) {
        state = IN;
        ++nw;
    }
duffymo
fonte
2
Obrigado! Na verdade, o código funcionou sem as chaves no segundo if :)
César
5
+1. Não apenas ambíguo, mas um tanto perigoso. Quando (se) você adicionar uma linha ao seu ifbloco mais tarde, se você esquecer de adicionar as chaves porque o seu bloco agora é mais de uma linha, pode demorar um pouco para depurar esse erro ...
The111
8
@ The111 Nunca, jamais, aconteceu comigo. Ainda não acredito que isso seja um problema real. Tenho usado o estilo sem braçadeiras por mais de uma década, nunca esqueci de adicionar as braçadeiras ao expandir o corpo de um bloco.
Konrad Rudolph
1
@ The111: Neste caso, alguns contribuidores de SO demoraram alguns minutos: P E se você é um programador capaz de adicionar declarações a uma ifcláusula e "esquecer" de atualizar as chaves, então, bem, você não um programador muito bom.
Lightness Races in Orbit
3

Uma maneira simples é usar colchetes como {} para cada um ife else:

if (c == '\n'){
    ++nl;
}
if (c == ' ' || c == '\n' || c == '\t')
{
    state = OUT;
}
else if (state == OUT) {
    state = IN;
    ++nw;
}
Nauman Khalid
fonte
2

Como outras respostas apontaram, o problema está entre #definee ponto e vírgula. Para minimizar esses problemas, sempre prefiro definir constantes numéricas como const int:

const int IN = 1;
const int OUT = 0;

Desta forma, você se livra de muitos problemas e possíveis problemas. É limitado por apenas duas coisas:

  1. Seu compilador tem que oferecer suporte const- o que em 1988 geralmente não era verdade, mas agora é suportado por todos os compiladores comumente usados. (AFAIK, o consté "emprestado" do C ++.)

  2. Você não pode usar essas constantes em alguns lugares especiais onde precisaria de uma constante semelhante a uma string. Mas acho que seu programa não é esse o caso.

Al Kepp
fonte
Uma alternativa que eu prefiro são enums - eles podem ser usados ​​em lugares especiais (como declarações de array) que const intnão podem em C.
Michael Burr