Apa yang salah dengan kode C 1988 ini?

94

Saya mencoba untuk mengumpulkan potongan kode ini dari buku "The C Programming Language" (K & R). Ini adalah versi sederhana dari program 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);
}

Dan saya mendapatkan kesalahan berikut:

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

Edisi ke-2 dari buku ini adalah dari tahun 1988 dan saya cukup baru mengenal C. Mungkin ada hubungannya dengan versi kompiler atau mungkin saya hanya berbicara omong kosong.

Saya telah melihat dalam kode C modern penggunaan mainfungsi yang berbeda:

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

Apakah ini standar baru atau apakah saya masih dapat menggunakan utama tanpa tipe?

César
sumber
4
Bukan sebuah jawaban, tetapi potongan kode lain untuk dilihat lebih dekat , || c = '\t'). Apakah itu tampak sama dengan kode lain di baris itu?
pengguna7116
58
32 suara positif untuk pertanyaan debugging + kesalahan ketik ?!
Lightness Races di Orbit
37
@ TomalakGeret'kal: Anda tahu, barang lama lebih dihargai (anggur, lukisan, kode C)
Sergio Tulentsev
16
@ César: Saya berhak untuk menyatakan pendapat saya, dan saya akan berterima kasih untuk tidak mencoba menyensornya. Ya, ini bukan situs web untuk men-debug kode Anda dan menyelesaikan kesalahan ketik Anda, yang merupakan masalah "dilokalkan" yang tidak akan pernah membantu orang lain. Ini adalah situs web untuk pertanyaan tentang bahasa pemrograman , bukan untuk melakukan debugging dasar dan pekerjaan referensi untuk Anda. Tingkat keahlian sama sekali tidak relevan. Baca FAQ, dan mungkin juga pertanyaan meta ini .
Lightness Races di Orbit
11
@ TomalakGeret'kal tentu saja Anda dapat mengungkapkan pendapat Anda dan saya tidak akan menyensor komentar Anda meskipun tidak membangun. Saya sudah membaca FAQ. Saya seorang programmer yang antusias menanyakan tentang masalah sebenarnya yang saya hadapi
César

Jawaban:

247

Masalah Anda ada pada definisi praprosesor Anda INdan OUT:

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

Perhatikan bagaimana Anda memiliki tanda titik koma di belakangnya di masing-masing. Ketika preprocessor mengembangkannya, kode Anda akan terlihat seperti:

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

Titik koma kedua itu menyebabkan elsetidak ada sebelumnya ifsebagai pertandingan, karena Anda tidak menggunakan tanda kurung. Jadi, hapus titik koma dari definisi preprocessor dari INdan OUT.

Pelajaran yang didapat di sini adalah bahwa pernyataan preprocessor tidak harus diakhiri dengan titik koma.

Selain itu, Anda harus selalu menggunakan kawat gigi!

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

Tidak ada elseambiguitas gantung dalam kode di atas.

pengguna7116
sumber
8
Untuk kejelasan, masalahnya bukanlah spasi, itu titik koma. Anda tidak membutuhkannya dalam pernyataan preprocessor.
Dan
@Dan terima kasih atas klarifikasinya! Dan titik koma memang masalahnya! Terima kasih teman-teman!
César
2
@ César: sama-sama. Saran bracing ini semoga bisa menghindarkan anda dari masalah dikemudian hari, tentunya sangat membantu saya!
pengguna7116
5
@ César: Ini juga merupakan ide yang baik untuk membiasakan menempatkan tanda kurung di sekitar makro karena Anda biasanya ingin makro dievaluasi terlebih dahulu. Dalam hal ini, tidak masalah karena nilainya adalah token tunggal, tetapi mengabaikan tanda kurung dapat menyebabkan hasil yang tidak terduga saat mendefinisikan ekspresi.
styfle
7
"tidak membutuhkan mereka"! = "seharusnya tidak memilikinya". yang pertama selalu benar; yang terakhir bergantung pada konteks dan merupakan masalah yang lebih relevan dalam skenario ini.
Lightness Races di Orbit
63

Masalah utama dengan kode ini adalah bahwa ini bukan kode dari K&R. Ini mencakup titik koma setelah definisi makro, yang tidak ada dalam buku, yang seperti yang ditunjukkan orang lain mengubah artinya.

Kecuali saat membuat perubahan dalam upaya memahami kode, Anda harus membiarkannya sampai Anda benar-benar memahaminya. Anda hanya dapat mengubah kode yang Anda pahami dengan aman.

Ini mungkin hanya salah ketik di pihak Anda, tetapi itu menggambarkan kebutuhan untuk memahami dan memperhatikan detail saat pemrograman.

jmoreno
sumber
9
Nasihat Anda tidak terlalu konstruktif bagi seseorang yang belajar membuat program. Memodifikasi kode persis seperti cara Anda memahami detail pemrograman.
pengguna7116
12
@sixlettervariables: Dan saat melakukannya, Anda harus tahu perubahan apa yang telah Anda buat, dan sesedikit mungkin perubahan. Jika OP membuat perubahan dengan sengaja, dan membuat perubahan sesedikit mungkin, dia mungkin tidak akan menanyakan pertanyaan ini, karena akan jelas baginya apa yang sedang terjadi. Dia akan mengubah makro untuk IN, tanpa kesalahan dan kemudian makro untuk OUT dengan dua kesalahan, yang kedua akan mengeluh tentang titik koma yang baru saja dia tambahkan.
jmoreno
5
Sepertinya kecuali Anda membuat kesalahan dengan menyertakan titik koma di akhir baris arahan preprocessor, Anda kemungkinan besar tidak akan tahu bahwa Anda tidak menyertakannya. Anda dapat menerima begitu saja, Anda dapat membaca banyak kode dan melihat bahwa kode tersebut sepertinya tidak pernah ada di sana. Atau, OP dapat mengacaukan dengan menyertakannya, bertanya tentang kesalahan "aneh", dan mencari tahu: oops, tidak perlu titik koma untuk arahan preprocessor! Ini adalah pemrograman, bukan episode Scared Straight.
pengguna7116
14
@sixlettervariables: Ya, tetapi ketika kodenya tidak berfungsi, langkah pertama yang jelas adalah pergi "oh, ok, lalu apa yang saya ubah tanpa alasan apa pun dari kode yang ditulis dalam buku oleh penemu C, mungkin adalah masalah. Saya akan membatalkannya nanti. "
Balapan Lightness di Orbit
3
mari kita lanjutkan diskusi ini dalam obrolan
pengguna7116
34

Tidak boleh ada titik koma setelah makro,

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

dan mungkin seharusnya begitu

if (c == ' ' || c == '\n' || c == '\t')
onemach
sumber
Terima kasih, titik koma adalah masalahnya. Yang kedua adalah salah ketik!
César
21
Lain kali, sisipkan kode persis yang Anda gunakan, langsung dari editor teks Anda.
Balapan Lightness di Orbit
@ TomalakGeret'kal baik saya tidak dan saya akan, tapi bagaimana Anda menemukan?
onemach
1
@onemach: Anda mengatakan bahwa ;itu adalah kesalahan ketik yang tidak memengaruhi masalah, yang berarti kesalahan ketik pada pertanyaan Anda, bukan pada kode yang sebenarnya Anda gunakan.
Lightness Races di Orbit
24

Definisi IN dan OUT akan terlihat seperti ini:

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

Titik koma menyebabkan masalah! Penjelasannya sederhana: baik IN dan OUT adalah arahan preprocessor, pada dasarnya kompilator akan mengganti semua kemunculan IN dengan 1 dan semua kemunculan OUT dengan 0 di kode sumber.

Karena kode asli memiliki titik koma setelah 1 dan 0, ketika IN dan OUT diganti dalam kode, tanda titik koma tambahan setelah angka menghasilkan kode yang tidak valid, misalnya baris ini:

else if (state == OUT)

Akhirnya terlihat seperti ini:

else if (state == 0;)

Tapi yang Anda inginkan adalah ini:

else if (state == 0)

Solusi: Hapus titik koma setelah angka dalam definisi aslinya.

Óscar López
sumber
8

Seperti yang Anda lihat, ada masalah di makro.

GCC memiliki opsi untuk berhenti setelah pra-pemrosesan. (-E) Opsi ini berguna untuk melihat hasil pra-pemrosesan. Faktanya, teknik ini penting jika Anda bekerja dengan basis kode besar di c / c ++. Biasanya makefile akan memiliki target untuk dihentikan setelah pra-pemrosesan.

Untuk referensi cepat: Pertanyaan SO mencakup opsi - Bagaimana cara melihat file sumber C / C ++ setelah preprocessing di Visual Studio? . Ini dimulai dengan vc ++, tetapi juga memiliki opsi gcc yang disebutkan di bawah .

Jayan
sumber
7

Bukan masalah, tetapi deklarasi main()juga bertanggal, seharusnya seperti ini.

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

Kompilator akan mengasumsikan nilai kembalian int untuk fungsi tanpa fungsi, dan saya yakin kompilator / penaut akan mengatasi kekurangan deklarasi untuk argc / argv dan kurangnya nilai kembalian, tetapi seharusnya ada di sana.

Tagihan
sumber
3
Itu buku yang bagus - satu dari dua buku berharga tentang C sejauh yang saya tahu. Saya cukup yakin bahwa edisi yang lebih baru sesuai dengan ANSI C (mungkin sebelum C99 ANSI C). Nilai lain sementara buku tentang C adalah Ahli Pemrograman C Rahasia C oleh Peter van der Linden.
Tagihan
Saya tidak pernah mengatakan itu. Saya hanya diberi komentar bahwa untuk menyesuaikannya dengan cara yang dilakukan saat ini, prinsip itu harus diubah.
Bill
4

Coba tambahkan tanda kurung kurawal di sekitar blok kode. Gaya K&R bisa jadi ambigu.

Lihat baris 18. Kompilator memberi tahu Anda di mana masalahnya.

    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
sumber
2
Terima kasih! Sebenarnya, kode bekerja tanpa kurung kurawal di if :)
César
5
+1. Bukan hanya ambigu tapi agak berbahaya. Ketika (jika) Anda menambahkan baris ke ifblok Anda nanti, jika Anda lupa menambahkan kurung kurawal karena blok Anda sekarang lebih dari satu baris, perlu beberapa saat untuk men-debug kesalahan itu ...
The111
8
@ The111 Tidak pernah, pernah, terjadi padaku. Saya masih tidak percaya bahwa ini adalah masalah nyata. Saya telah menggunakan gaya brace-less selama lebih dari satu dekade, saya tidak pernah lupa untuk menambahkan brace saat melebarkan badan balok.
Konrad Rudolph
1
@ The111: Dalam kasus ini, diperlukan beberapa menit kontributor SO: P Dan jika Anda seorang programmer yang mampu menambahkan pernyataan ke ifklausa dan "lupa" untuk memperbarui tanda kurung kurawal, yah, Anda tidak programmer yang sangat baik.
Balapan Ringan di Orbit
3

Cara sederhana adalah dengan menggunakan tanda kurung seperti {} untuk masing-masing ifdan else:

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

Seperti jawaban lain yang ditunjukkan, masalahnya ada di #definedan titik koma. Untuk meminimalkan masalah ini saya selalu lebih suka mendefinisikan konstanta bilangan sebagai const int:

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

Dengan cara ini Anda menyingkirkan banyak masalah dan kemungkinan masalah. Itu dibatasi hanya oleh dua hal:

  1. Kompilator Anda harus mendukung const- yang pada tahun 1988 umumnya tidak benar, tetapi sekarang didukung oleh semua kompiler yang umum digunakan. (AFAIK constadalah "dipinjam" dari C ++.)

  2. Anda tidak dapat menggunakan konstanta ini di beberapa tempat khusus yang membutuhkan konstanta seperti string. Tapi saya pikir program Anda tidak seperti itu.

Al Kepp
sumber
Alternatif yang saya sukai adalah enum - mereka dapat digunakan di tempat-tempat khusus (seperti deklarasi array) yang const inttidak bisa di C.
Michael Burr