¿Qué pasa con este código C de 1988?

94

Estoy intentando compilar este código del libro "El lenguaje de programación C" (K & R). Es una versión básica del programa 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);
}

Y recibo el siguiente error:

$ 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 segunda edición de este libro es de 1988 y soy bastante nuevo en C. Tal vez tenga que ver con la versión del compilador o tal vez solo estoy diciendo tonterías.

He visto en el código C moderno un uso diferente de la mainfunción:

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

¿Es este un nuevo estándar o todavía puedo usar un main sin tipo?

César
fuente
4
No es una respuesta, sino otra pieza de código para mirar más de cerca || c = '\t'). ¿Parece lo mismo que el otro código en esa línea?
user7116
58
¿32 votos a favor para una pregunta de depuración + error tipográfico?
Lightness Races in Orbit
37
@ TomalakGeret'kal: ya sabes, las cosas viejas se valoran más (vino, pinturas, código C)
Sergio Tulentsev
16
@ César: Estoy en mi derecho de expresar mi opinión y te agradeceré que no intentes censurarla. Da la casualidad de que sí, este no es un sitio web para depurar su código y resolver sus errores tipográficos, que son problemas "localizados" que nunca ayudarán a nadie más. Es un sitio web para preguntas sobre lenguajes de programación , no para hacer su trabajo básico de depuración y referencia por usted. El nivel de habilidad es completamente irrelevante. Lea las preguntas frecuentes y quizás también esta meta pregunta .
Lightness Races in Orbit
11
@ TomalakGeret'kal, por supuesto, puede expresar su opinión y no censuraré su comentario a pesar de ser poco constructivo. Ya leí las preguntas frecuentes. Soy un programador entusiasta que pregunta sobre un problema real al que me enfrento
César

Respuestas:

247

Su problema es con las definiciones de su preprocesador de INy OUT:

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

Observe cómo tiene un punto y coma al final en cada uno de estos. Cuando el preprocesador los expande, su código se verá más o menos como:

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

Ese segundo punto y coma hace elseque no tenga un anterior ifcomo coincidencia, porque no está usando llaves. Por lo tanto, elimine el punto y coma de las definiciones del preprocesador de INy OUT.

La lección aprendida aquí es que las declaraciones del preprocesador no tienen que terminar con un punto y coma.

Además, ¡siempre debes usar aparatos ortopédicos!

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

No hay elseambigüedad pendiente en el código anterior.

usuario7116
fuente
8
Para mayor claridad, el problema no es el espaciado, son los puntos y comas. No los necesita en declaraciones de preprocesador.
Dan
@Dan ¡gracias por la aclaración! ¡Y los puntos y comas eran el problema! ¡Gracias chicos!
César
2
@ César: de nada. Con suerte, la sugerencia de refuerzo lo mantendrá fuera de problemas en el futuro, ¡ciertamente me ha ayudado!
user7116
5
@ César: También es una buena idea acostumbrarse a poner paréntesis alrededor de las macros, ya que generalmente desea que la macro se evalúe primero. En este caso, no importa, ya que el valor es un solo token, pero omitir los paréntesis puede generar resultados inesperados al definir una expresión.
styfle
7
"don't need them"! = "No debería tenerlos". lo primero es siempre cierto; este último depende del contexto y es el tema más pertinente en este escenario.
Lightness Races in Orbit
63

El principal problema con este código es que es no el código de K & R. Incluye punto y coma después de las definiciones de macros, que no estaban presentes en el libro, que como otros han señalado cambia el significado.

Excepto cuando haga un cambio en un intento de comprender el código, debe dejarlo solo hasta que lo comprenda. Solo puede modificar de forma segura el código que comprenda.

Esto probablemente fue solo un error tipográfico de su parte, pero ilustra la necesidad de comprender y prestar atención a los detalles al programar.

jmoreno
fuente
9
Su consejo no es muy constructivo para alguien que está aprendiendo a programar. Modificar el código es precisamente cómo se comprenden los detalles de la programación.
user7116
12
@sixlettervariables: Y al hacerlo, debe saber qué cambios ha realizado y realizar la menor cantidad de cambios posible. Si el OP hubiera realizado los cambios deliberadamente y realizado el menor número posible de cambios, probablemente no habría hecho esta pregunta, ya que le habría quedado claro lo que estaba pasando. Habría cambiado la macro por IN, sin errores y luego la macro por OUT con los dos errores, el segundo de los cuales sería quejarse del punto y coma que acababa de agregar.
jmoreno
5
Parece que, a menos que cometa el error de incluir un punto y coma al final de una línea de directiva de preprocesador, probablemente no sepa que no debe incluirlos. Podrías tomarlo al pie de la letra, podrías leer muchos códigos y notar que nunca parecen estar ahí. O bien, el OP podría equivocarse al incluirlos, preguntar sobre el error "extraño" y descubrir: ¡Ups, no se requieren puntos y comas para las directivas del preprocesador! Esto es programación, no un episodio de Scared Straight.
user7116
14
@sixlettervariables: Sí, pero cuando el código no funciona, el primer paso obvio es decir "oh, está bien, entonces lo que cambié sin ningún motivo del código escrito en un libro por el inventor de C, fue probablemente el problema. Lo deshaceré entonces ".
Lightness Races in Orbit
34

No debe haber punto y coma después de las macros,

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

y probablemente debería ser

if (c == ' ' || c == '\n' || c == '\t')
onemach
fuente
Gracias, los puntos y comas fueron el problema. ¡El segundo fue un error tipográfico!
César
21
La próxima vez, pegue el código exacto que utiliza, directamente desde su editor de texto.
Lightness Races in Orbit
@ TomalakGeret'kal bueno, no lo hice y lo haré, pero ¿cómo lo encontraste?
onemach
1
@onemach: Dijiste que ;era un error tipográfico que no afectó el problema, lo que significa un error tipográfico en tu pregunta en lugar del código que realmente usaste.
Lightness Races in Orbit
24

Las definiciones de IN y OUT deberían verse así:

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

¡Los puntos y comas estaban causando el problema! La explicación es simple: tanto IN como OUT son directivas de preprocesador, esencialmente el compilador reemplazará todas las apariciones de IN con un 1 y todas las apariciones de OUT con un 0 en el código fuente.

Dado que el código original tenía un punto y coma después del 1 y el 0, cuando IN y OUT se reemplazaron en el código, el punto y coma adicional después del número produjo un código no válido, por ejemplo, esta línea:

else if (state == OUT)

Terminó luciendo así:

else if (state == 0;)

Pero lo que querías era esto:

else if (state == 0)

Solución: elimine el punto y coma después de los números en la definición original.

Óscar López
fuente
8

Como ve, hubo un problema en las macros.

GCC tiene la opción de detenerse después del preprocesamiento. (-E) Esta opción es útil para ver el resultado del preprocesamiento. De hecho, la técnica es importante si está trabajando con una base de código grande en c / c ++. Normalmente, los archivos MAKE tendrán un objetivo para detenerse después del preprocesamiento.

Para una referencia rápida: La pregunta SO cubre las opciones: ¿Cómo veo un archivo fuente C / C ++ después del preprocesamiento en Visual Studio? . Comienza con vc ++, pero también tiene las opciones de gcc que se mencionan a continuación .

Jayan
fuente
7

No es exactamente un problema, pero la declaración de main()también está fechada, debería ser algo así.

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

El compilador asumirá un valor de retorno int para una función sin uno, y estoy seguro de que el compilador / enlazador solucionará la falta de declaración para argc / argv y la falta de valor de retorno, pero deberían estar ahí.

Cuenta
fuente
3
Ese es un buen libro, uno de los dos únicos libros valiosos sobre C que yo sepa. Estoy bastante seguro de que las ediciones más recientes cumplen con ANSI C (probablemente antes de C99 ANSI C). El otro libro que vale la pena sobre C es Expert C Programming Deep C Secrets de Peter van der Linden.
Proyecto de ley
Yo nunca dije que era. Simplemente me comentaron que para adecuarlo a la forma en que se hacen las cosas hoy en día, esa principal debería cambiarse.
Proyecto de ley
4

Intente agregar llaves explícitas alrededor de bloques de código. El estilo de K&R puede ser ambiguo.

Mire la línea 18. El compilador le dice dónde está el 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
fuente
2
¡Gracias! En realidad, el código funcionó sin las llaves en el segundo si :)
César
5
+1. No solo ambiguo sino algo peligroso. Cuando (si) agrega una línea a su ifbloque más adelante, si olvida agregar las llaves porque su bloque ahora tiene más de una línea, puede tomar un tiempo depurar ese error ...
The111
8
@ The111 Nunca, nunca, me pasó. Todavía no creo que esto sea un problema real. He estado usando el estilo sin tirantes durante más de una década, nunca olvidé agregar los tirantes al expandir el cuerpo de un bloque.
Konrad Rudolph
1
@ The111: En este caso, algunos colaboradores de SO tardaron unos minutos: P Y si eres un programador que es capaz de agregar declaraciones a una ifcláusula y "olvidar" actualizar las llaves, entonces, bueno, no lo eres un muy buen programador.
Lightness Races in Orbit
3

Una forma sencilla es usar corchetes como {} para cada uno ify else:

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

Como señalaron otras respuestas, el problema está en #definepunto y coma. Para minimizar estos problemas, siempre prefiero definir las constantes numéricas como const int:

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

De esta forma te deshaces de muchos problemas y posibles problemas. Está limitado por solo dos cosas:

  1. Su compilador tiene que ser compatible const, lo que en 1988 no era cierto en general, pero ahora es compatible con todos los compiladores de uso común. (AFAIK el constes "prestado" de C ++.)

  2. No puede usar estas constantes en algunos lugares especiales donde necesitaría una constante similar a una cadena. Pero creo que su programa no es ese caso.

Al Kepp
fuente
Una alternativa que prefiero son las enumeraciones: se pueden usar en lugares especiales (como declaraciones de matriz) que const intno pueden en C.
Michael Burr