Quel est le problème avec ce code C de 1988?

94

J'essaye de compiler ce morceau de code du livre "The C Programming Language" (K & R). Il s'agit d'une version simple du programme UNIX wc:

#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);
}

Et j'obtiens l'erreur suivante:

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

La 2ème édition de ce livre date de 1988 et je suis assez nouveau en C. Peut-être que cela a à voir avec la version du compilateur ou peut-être que je dis juste des bêtises.

J'ai vu dans le code C moderne une utilisation différente de la mainfonction:

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

Est-ce une nouvelle norme ou puis-je toujours utiliser un main sans type?

César
la source
4
Pas une réponse, mais un autre morceau de code à regarder de plus près, || c = '\t'). Cela ressemble-t-il à l'autre code sur cette ligne?
user7116
58
32 votes positifs pour une question de débogage + faute de frappe?!
Courses de légèreté en orbite
37
@ TomalakGeret'kal: vous savez, les vieux trucs sont plus valorisés (vin, peintures, code C)
Sergio Tulentsev
16
@ César: J'ai tout à fait le droit d'exprimer mon opinion, et je vous remercie de ne pas essayer de le censurer. En l'occurrence, oui, ce n'est pas un site Web pour déboguer votre code et résoudre vos erreurs typographiques, qui sont des problèmes "localisés" qui n'aideront jamais personne d'autre. C'est un site Web pour les questions sur les langages de programmation , pas pour faire votre travail de débogage de base et de référence pour vous. Le niveau de compétence est complètement hors de propos. Lisez la FAQ, et peut-être aussi cette méta-question .
Courses de légèreté en orbite
11
@ TomalakGeret'kal bien sûr, vous pouvez exprimer votre opinion et je ne censurerai pas votre commentaire en dépit d'être peu constructif. J'ai déjà lu la FAQ. Je suis un programmeur passionné qui pose des questions sur un problème réel auquel je suis confronté
César

Réponses:

247

Votre problème concerne vos définitions de préprocesseur de INet OUT:

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

Remarquez comment vous avez un point-virgule de fin dans chacun de ces éléments. Lorsque le préprocesseur les développe, votre code ressemblera à peu près à:

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

Ce deuxième point-virgule fait que le elsen'a aucun précédent ifcomme correspondance, car vous n'utilisez pas d'accolades. Supprimez donc les points-virgules des définitions de préprocesseur de INet OUT.

La leçon apprise ici est que les instructions du préprocesseur ne doivent pas nécessairement se terminer par un point-virgule.

De plus, vous devriez toujours utiliser des accolades!

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

Il n'y a pas d' elseambiguïté suspendue dans le code ci-dessus.

user7116
la source
8
Pour plus de clarté, le problème n'est pas l'espacement, ce sont les points-virgules. Vous n'en avez pas besoin dans les instructions de préprocesseur.
Dan
@Dan merci pour la clarification! Et les points-virgules étaient bien le problème! Merci les gars!
César
2
@ César: de rien. J'espère que la suggestion vivifiante vous évitera des ennuis à l'avenir, m'a certainement aidé!
user7116
5
@ César: C'est aussi une bonne idée de s'habituer à mettre des parenthèses autour des macros puisque vous voulez généralement que la macro soit évaluée en premier. Dans ce cas, cela n'a pas d'importance car la valeur est un seul jeton, mais le fait d'omettre des parenthèses peut entraîner des résultats inattendus lors de la définition d'une expression.
styfle
7
"don't need them"! = "ne devrait pas en avoir". le premier est toujours vrai; ce dernier dépend du contexte et constitue la question la plus pertinente dans ce scénario.
Courses de légèreté en orbite
63

Le principal problème avec ce code est qu'il ne s'agit pas du code de K&R. Il comprend des points-virgules après les définitions de macros, qui n'étaient pas présentes dans le livre, qui, comme d'autres l'ont souligné, modifie la signification.

Sauf lorsque vous effectuez un changement pour tenter de comprendre le code, vous devez le laisser seul jusqu'à ce que vous le compreniez. Vous ne pouvez modifier en toute sécurité que le code que vous comprenez.

Ce n'était probablement qu'une faute de frappe de votre part, mais cela illustre le besoin de compréhension et d'attention aux détails lors de la programmation.

jmoreno
la source
9
Vos conseils ne sont pas très constructifs pour quelqu'un qui apprend à programmer. La modification du code est précisément la façon dont vous comprenez les détails de la programmation.
user7116
12
@sixlettervariables: Et ce faisant, vous devez savoir quelles modifications vous avez apportées et faire le moins de modifications possible. Si le PO avait délibérément apporté les changements et apporté le moins de changements possible, il n'aurait probablement pas posé cette question, car il lui aurait été clair ce qui se passait. Il aurait changé la macro pour IN, sans erreur, puis la macro pour OUT avec les deux erreurs, la seconde se plaignant du point-virgule qu'il venait d'ajouter.
jmoreno
5
Il semble qu'à moins que vous ne commettiez l'erreur d'inclure un point-virgule à la fin d'une ligne de directive de préprocesseur, vous ne sauriez probablement pas que vous ne devez pas les inclure. Vous pourriez le prendre pour argent comptant, vous pourriez lire beaucoup de code et remarquer qu'ils ne semblent jamais être là. Ou bien, l'OP pourrait gâcher en les incluant, poser des questions sur l'erreur "bizarre" et découvrir: oups, pas de point-virgule requis pour les directives du préprocesseur! C'est de la programmation, pas un épisode de Scared Straight.
user7116
14
@sixlettervariables: Oui, mais quand le code ne fonctionne pas, la première étape évidente est de dire "oh, ok, alors ce que j'ai changé sans aucune raison à partir du code écrit dans un livre par l'inventeur de C, était probablement le problème. Je vais simplement annuler cela alors. "
Courses de légèreté en orbite
34

Il ne devrait y avoir aucun point-virgule après les macros,

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

et ça devrait probablement être

if (c == ' ' || c == '\n' || c == '\t')
onemach
la source
Merci, les points-virgules étaient le problème. Le 2ème était une faute de frappe!
César du
21
La prochaine fois, veuillez coller le code exact que vous utilisez, directement depuis votre éditeur de texte.
Courses de légèreté en orbite
@ TomalakGeret'kal bien je n'ai pas et je le ferai, mais comment avez-vous trouvé?
onemach
1
@onemach: Vous avez dit que ;c'était une faute de frappe qui n'a pas affecté le problème, ce qui signifie une faute de frappe dans votre question plutôt que dans le code que vous avez réellement utilisé.
Courses de légèreté en orbite
24

Les définitions de IN et OUT devraient ressembler à ceci:

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

Les points-virgules causaient le problème! L'explication est simple: IN et OUT sont des directives de préprocesseur, essentiellement le compilateur remplacera toutes les occurrences de IN par un 1 et toutes les occurrences de OUT par un 0 dans le code source.

Étant donné que le code d'origine avait un point-virgule après le 1 et le 0, lorsque IN et OUT ont été remplacés dans le code, le point-virgule supplémentaire après le numéro a produit un code invalide, par exemple cette ligne:

else if (state == OUT)

J'ai fini par ressembler à ceci:

else if (state == 0;)

Mais ce que vous vouliez, c'était ceci:

else if (state == 0)

Solution: supprimez le point-virgule après les nombres dans la définition d'origine.

Óscar López
la source
8

Comme vous le voyez, il y avait un problème dans les macros.

GCC a la possibilité de s'arrêter après le prétraitement.(-E) Cette option est utile pour voir le résultat du prétraitement. En fait, la technique est importante si vous travaillez avec une grande base de code en c / c ++. En général, les makefiles auront une cible à arrêter après le prétraitement.

Pour une référence rapide: La question SO couvre les options - Comment puis-je voir un fichier source C / C ++ après le prétraitement dans Visual Studio? . Il commence par vc ++, mais a également des options gcc mentionnées ci-dessous .

Jayan
la source
7

Pas exactement un problème, mais la déclaration de main()est également datée, cela devrait ressembler à quelque chose comme ça.

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

Le compilateur supposera une valeur de retour int pour une fonction sans une, et je suis sûr que le compilateur / éditeur de liens contournera le manque de déclaration pour argc / argv et le manque de valeur de retour, mais ils devraient être là.

Facture
la source
3
C'est un bon livre - l'un des deux seuls livres valables sur C pour autant que je sache. Je suis à peu près sûr que les nouvelles éditions sont conformes à ANSI C (probablement avant C99 ANSI C). L'autre livre intéressant sur C est Expert C Programming Deep C Secrets de Peter van der Linden.
Projet de loi du
Je n'ai jamais dit que c'était le cas. On m'a simplement fait remarquer que pour que cela corresponde à la façon dont les choses sont faites aujourd'hui, il faudrait changer ce principe.
Projet de loi du
4

Essayez d'ajouter des accolades explicites autour des blocs de code. Le style K&R peut être ambigu.

Regardez la ligne 18. Le compilateur vous indique où est le problème.

    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
la source
2
Merci! En fait, le code fonctionnait sans les accolades dans le second if :)
César
5
+1. Pas seulement ambigu mais quelque peu dangereux. Quand (si) vous ajoutez une ligne à votre ifbloc plus tard, si vous oubliez d'ajouter les accolades parce que votre bloc est maintenant plus d'une ligne, cela peut prendre un certain temps pour déboguer cette erreur ...
The111
8
@ The111 Il ne m'est jamais arrivé, jamais. Je ne crois toujours pas que ce soit un vrai problème. J'utilise le style sans accolade depuis plus d'une décennie, je n'ai jamais oublié une seule fois d'ajouter les accolades lors de l'expansion du corps d'un bloc.
Konrad Rudolph
1
@ The111: Dans ce cas, il a fallu quelques minutes à quelques contributeurs SO: P Et si vous êtes un programmeur capable d'ajouter des instructions à une ifclause et d '"oublier" de mettre à jour les accolades alors, eh bien, vous n'êtes pas un très bon programmeur.
Courses de légèreté en orbite
3

Un moyen simple consiste à utiliser des crochets comme {} pour chacun ifet else:

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

Comme d'autres réponses l'ont souligné, le problème est entre les #definepoints et les points-virgules. Pour minimiser ces problèmes, je préfère toujours définir les constantes numériques comme const int:

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

De cette façon, vous vous débarrassez de nombreux problèmes et problèmes éventuels. Il est limité par seulement deux choses:

  1. Votre compilateur doit prendre en charge const- ce qui n'était généralement pas vrai en 1988, mais maintenant il est pris en charge par tous les compilateurs couramment utilisés. (AFAIK le constest "emprunté" à C ++.)

  2. Vous ne pouvez pas utiliser ces constantes dans certains endroits spéciaux où vous auriez besoin d'une constante de type chaîne. Mais je pense que votre programme n'est pas ce cas.

Al Kepp
la source
Une alternative que je préfère est les enums - ils peuvent être utilisés dans les endroits spéciaux (comme les déclarations de tableau) qui const intne peuvent pas en C.
Michael Burr