Mengapa kompilator tidak melaporkan titik koma yang hilang?

115

Saya memiliki program sederhana ini:

#include <stdio.h>

struct S
{
    int i;
};

void swap(struct S *a, struct S *b)
{
    struct S temp;
    temp = *a    /* Oops, missing a semicolon here... */
    *a = *b;
    *b = temp;
}

int main(void)
{
    struct S a = { 1 };
    struct S b = { 2 };

    swap(&a, &b);
}

Seperti yang terlihat di eg ideone.com ini memberikan kesalahan:

prog.c: In function 'swap':
prog.c:12:5: error: invalid operands to binary * (have 'struct S' and 'struct S *')
     *a = *b;
     ^

Mengapa kompilator tidak mendeteksi titik koma yang hilang?


Catatan: Pertanyaan ini dan jawabannya dimotivasi oleh pertanyaan ini . Meskipun ada pertanyaan lain yang serupa dengan ini, saya tidak menemukan apa pun yang menyebutkan kapasitas bentuk bebas dari bahasa C yang menyebabkan ini dan kesalahan terkait.

Beberapa programmer
sumber
16
Apa yang memotivasi posting ini?
R Sahu
10
@TavianBarnes Dapat ditemukan. Pertanyaan lain tidak dapat ditemukan saat menelusuri jenis masalah ini. Itu bisa diedit seperti itu, tapi itu akan membutuhkan perubahan sedikit terlalu banyak, menjadikannya pertanyaan IMO yang sama sekali berbeda.
Beberapa pria programmer
4
@TavianBarnes: Pertanyaan asli menanyakan kesalahan. Pertanyaan ini menanyakan mengapa kompilator tampaknya (setidaknya bagi OP) salah melaporkan lokasi kesalahan.
TonyK
80
Tunjuk untuk merenungkan: jika kompiler dapat secara sistematis mendeteksi titik koma yang hilang, bahasa tersebut tidak memerlukan titik koma untuk memulai.
Euro Micelli
5
Tugas penyusun adalah melaporkan kesalahan tersebut. Tugas Anda adalah mencari tahu apa yang harus diubah untuk memperbaiki kesalahan tersebut.
David Schwartz

Jawaban:

213

C adalah bahasa bentuk bebas . Itu berarti Anda dapat memformatnya dengan banyak cara dan itu akan tetap menjadi program yang legal.

Misalnya pernyataan seperti

a = b * c;

bisa ditulis seperti

a=b*c;

atau suka

a
=
b
*
c
;

Jadi ketika kompiler melihat garis

temp = *a
*a = *b;

menurutnya itu berarti

temp = *a * a = *b;

Itu tentu saja bukan ekspresi yang valid dan kompilator akan mengeluh tentang hal itu alih-alih titik koma yang hilang. Alasan tidak valid adalah karena amerupakan pointer ke struktur, jadi *a * amencoba mengalikan struktur instance ( *a) dengan pointer ke struktur ( a).

Meskipun kompilator tidak dapat mendeteksi titik koma yang hilang, ia juga melaporkan kesalahan yang sama sekali tidak terkait pada baris yang salah. Ini penting untuk diperhatikan karena tidak peduli seberapa banyak Anda melihat baris di mana kesalahan dilaporkan, tidak ada kesalahan di sana. Terkadang masalah seperti ini mengharuskan Anda untuk melihat baris sebelumnya untuk melihat apakah mereka baik-baik saja dan tanpa kesalahan.

Terkadang Anda bahkan harus melihat file lain untuk menemukan kesalahannya. Misalnya jika file header menentukan struktur yang terakhir dilakukan di file header, dan titik koma yang mengakhiri struktur tidak ada, maka kesalahan tidak akan ada di file header tetapi di file yang menyertakan file header.

Dan terkadang menjadi lebih buruk: jika Anda menyertakan dua (atau lebih) file header, dan yang pertama berisi deklarasi yang tidak lengkap, kemungkinan besar kesalahan sintaks akan ditunjukkan di file header kedua.


Terkait hal ini adalah konsep kesalahan tindak lanjut . Beberapa kesalahan, biasanya karena titik koma hilang, dilaporkan sebagai beberapa kesalahan. Inilah mengapa penting untuk memulai dari atas saat memperbaiki kesalahan, karena memperbaiki kesalahan pertama mungkin membuat beberapa kesalahan hilang.

Hal ini tentu saja dapat menyebabkan perbaikan satu kesalahan pada satu waktu dan seringnya mengkompilasi ulang yang dapat merepotkan dengan proyek besar. Mengenali kesalahan tindak lanjut semacam itu adalah sesuatu yang datang dengan pengalaman, dan setelah melihatnya beberapa kali, lebih mudah untuk menggali kesalahan sebenarnya dan memperbaiki lebih dari satu kesalahan per kompilasi ulang.

Joachim Pileborg
sumber
16
Di C ++, temp = *a * a = *b bisa menjadi ekspresi yang valid jika operator*kelebihan beban. (Namun, pertanyaannya ditandai sebagai "C".)
dan04
13
@ dan04: Jika seseorang benar-benar melakukan itu ... TIDAK!
Kevin
2
1 untuk saran tentang (a) dimulai dengan kesalahan pertama yang dilaporkan; dan (b) melihat ke belakang dari mana kesalahan dilaporkan. Anda tahu bahwa Anda adalah seorang programmer sejati ketika Anda secara otomatis melihat ke baris sebelumnya di mana kesalahan dilaporkan :-)
TripeHound
@TripeHound TERUTAMA ketika ada jumlah kesalahan yang sangat besar, atau baris yang sebelumnya dikompilasi melempar kesalahan ...
Tin Wizard
1
Seperti yang biasanya terjadi dengan meta, seseorang sudah bertanya - meta.stackoverflow.com/questions/266663/...
StoryTeller - Unslander Monica
27

Mengapa kompilator tidak mendeteksi titik koma yang hilang?

Ada tiga hal yang perlu diingat.

  1. Akhiran baris di C hanyalah spasi kosong biasa.
  2. *di C dapat berupa operator uner dan biner. Sebagai operator unary artinya "dereferensi", sebagai operator biner artinya "perkalian".
  3. Perbedaan antara operator unary dan binary ditentukan dari konteks di mana mereka terlihat.

Hasil dari kedua fakta ini adalah saat kita mengurai.

 temp = *a    /* Oops, missing a semicolon here... */
 *a = *b;

Yang pertama dan terakhir *diartikan sebagai satu tetapi yang kedua *diartikan sebagai biner. Dari perspektif sintaks, ini terlihat OK.

Hanya setelah penguraian ketika kompilator mencoba menafsirkan operator dalam konteks jenis operannya, kesalahan akan terlihat.

plugwash
sumber
4

Beberapa jawaban bagus diatas, tapi akan saya uraikan.

temp = *a *a = *b;

Ini sebenarnya adalah kasus di x = y = z;mana keduanya xdan ydiberi nilai z.

Apa yang Anda katakan adalah the contents of address (a times a) become equal to the contents of b, as does temp.

Singkatnya, *a *a = <any integer value>adalah pernyataan yang valid. Seperti disebutkan sebelumnya, yang pertama *dereferensi penunjuk, sedangkan yang kedua mengalikan dua nilai.

Mawg mengatakan kembalikan Monica
sumber
3
Dereferensi mengambil prioritas, jadi (isi dari alamat a) kali (penunjuk ke a). Anda bisa tahu, karena kesalahan kompilasi mengatakan "operan tidak valid ke biner * (memiliki 'struct S' dan 'struct S *')" yang merupakan dua tipe tersebut.
keren
Saya membuat kode sebelum C99, jadi tidak ada masalah :-) Tetapi Anda benar-benar membuat poin yang baik (+1), meskipun urutan tugas bukanlah inti dari jawaban saya
Mawg mengatakan kembalikan Monica
1
Tetapi dalam kasus ini, ybahkan bukan variabel, itu adalah ekspresi *a *a, dan Anda tidak dapat menetapkan hasil perkalian.
Barmar
@Barmar memang tetapi compiler tidak sampai sejauh itu, telah diputuskan bahwa operan ke "binary *" tidak valid sebelum melihat operator penugasan.
plugwash
3

Sebagian besar penyusun mengurai file sumber secara berurutan, dan melaporkan baris tempat mereka menemukan ada sesuatu yang salah. 12 baris pertama dari program C Anda bisa menjadi awal dari program C yang valid (bebas kesalahan). 13 baris pertama program Anda tidak bisa. Beberapa kompiler akan mencatat lokasi hal-hal yang mereka temui yang bukan merupakan kesalahan itu sendiri, dan dalam banyak kasus tidak akan memicu kesalahan nanti dalam kode, tetapi mungkin tidak valid jika dikombinasikan dengan yang lain. Sebagai contoh:

int foo;
...
float foo;

Pernyataan int foo;itu sendiri akan baik-baik saja. Begitu juga deklarasi float foo;. Beberapa kompiler dapat merekam nomor baris di mana deklarasi pertama muncul, dan mengaitkan pesan informasional dengan baris tersebut, untuk membantu programmer mengidentifikasi kasus-kasus di mana definisi sebelumnya sebenarnya salah. Penyusun juga dapat menyimpan nomor baris yang terkait dengan sesuatu seperti a do, yang dapat dilaporkan jika terkait whiletidak muncul di tempat yang tepat. Untuk kasus di mana kemungkinan lokasi masalah akan tepat sebelum baris di mana kesalahan ditemukan, bagaimanapun, penyusun umumnya tidak repot-repot menambahkan laporan tambahan untuk posisi tersebut.

supercat
sumber