Mengapa pernyataan tanpa pengaruh dianggap legal dalam C?

13

Maafkan jika pertanyaan ini naif. Pertimbangkan program berikut:

#include <stdio.h>

int main() {
  int i = 1;
  i = i + 2;
  5;
  i;
  printf("i: %d\n", i);
}

Dalam contoh di atas, pernyataan 5;dan i;tampak benar-benar berlebihan, namun kode mengkompilasi tanpa peringatan atau kesalahan secara default (namun, gcc tidak memberikan warning: statement with no effect [-Wunused-value]peringatan ketika dijalankan dengan -Wall). Mereka tidak memiliki efek pada sisa program, jadi mengapa mereka dianggap pernyataan yang valid di tempat pertama? Apakah kompiler mengabaikannya? Apakah ada manfaat untuk mengizinkan pernyataan seperti itu?

AW
sumber
5
Apa manfaat dari melarang pernyataan seperti itu?
Mooing Duck
2
Ekspresi apa pun bisa berupa pernyataan dengan meletakkannya ;. Ini akan menyulitkan bahasa untuk menambahkan lebih banyak aturan tentang kapan ekspresi tidak bisa menjadi pernyataan
MM
3
Apakah Anda lebih suka kode Anda gagal dikompilasi karena Anda mengabaikan nilai pengembalian printf()? Pernyataan itu 5;pada dasarnya mengatakan "melakukan apa pun yang 5tidak (tidak ada) dan mengabaikan hasilnya. Pernyataan Anda printf(...)adalah 'melakukan apa pun yang printf(...)dilakukannya dan mengabaikan hasil (nilai kembali dari printf())'. C memperlakukan orang-orang yang sama. Hal ini juga memungkinkan untuk kode seperti (void) i;di mana iadalah parameter ke fungsi yang Anda gunakan voiduntuk menandainya sebagai sengaja tidak digunakan
Andrew Henle
1
@AndrewHenle: Itu tidak persis sama, karena panggilan printf()memang memiliki efek, bahkan jika Anda mengabaikan nilai itu akhirnya kembali. Sebaliknya 5;tidak memiliki efek sama sekali.
Nate Eldredge
1
Karena Dennis Ritchie, dan dia tidak ada untuk memberi tahu kami.
user207421

Jawaban:

10

Satu manfaat untuk mengizinkan pernyataan seperti itu adalah dari kode yang dibuat oleh makro atau program lain, daripada ditulis oleh manusia.

Sebagai contoh, bayangkan fungsi int do_stuff(void)yang seharusnya mengembalikan 0 pada keberhasilan atau -1 pada kegagalan. Bisa jadi dukungan untuk "barang" adalah opsional, sehingga Anda dapat memiliki file header yang berfungsi

#if STUFF_SUPPORTED
#define do_stuff() really_do_stuff()
#else
#define do_stuff() (-1)
#endif

Sekarang bayangkan beberapa kode yang ingin melakukan hal-hal jika memungkinkan, tetapi mungkin atau mungkin tidak benar-benar peduli apakah itu berhasil atau gagal:

void func1(void) {
    if (do_stuff() == -1) {
        printf("stuff did not work\n");
    }
}

void func2(void) {
    do_stuff(); // don't care if it works or not
    more_stuff();
}

Ketika STUFF_SUPPORTED0, preprocessor akan memperluas panggilan func2ke pernyataan yang baru saja dibaca

    (-1);

dan karenanya kompiler pass akan melihat hanya semacam pernyataan "berlebihan" yang tampaknya mengganggu Anda. Namun apa lagi yang bisa dilakukan seseorang? Jika Anda #define do_stuff() // nothing, maka kode di func1akan pecah. (Dan Anda masih akan memiliki pernyataan kosong func2yang baru saja dibaca ;, yang mungkin bahkan lebih berlebihan.) Di sisi lain, jika Anda harus benar-benar mendefinisikan do_stuff()fungsi yang mengembalikan -1, Anda dapat mengeluarkan biaya panggilan fungsi tanpa alasan.

Nate Eldredge
sumber
Versi yang lebih klasik (atau maksud saya versi umum) dari no-op adalah ((void)0).
Jonathan Leffler
Contoh yang bagus dari ini adalah assert.
Neil
3

Pernyataan Sederhana dalam C diakhiri dengan titik koma.

Pernyataan Sederhana dalam C adalah ekspresi. Ekspresi adalah kombinasi dari variabel, konstanta dan operator. Setiap ekspresi menghasilkan beberapa nilai tipe tertentu yang dapat ditetapkan ke variabel.

Setelah mengatakan bahwa beberapa "kompiler pintar" mungkin membuang 5; dan saya; pernyataan.

J. Dumbass
sumber
Saya tidak bisa membayangkan kompiler yang akan melakukan apa pun dengan pernyataan itu selain membuangnya. Apa lagi yang bisa dilakukan dengan mereka?
Jeremy Friesner
@JeremyFriesner: Kompiler yang sangat sederhana dan tidak mengoptimalkan mungkin menghasilkan kode untuk menghitung nilai dan memasukkan hasilnya dalam register (dari titik mana itu akan diabaikan).
Nate Eldredge
Standar C bukan istilah "pernyataan sederhana". Sebuah pernyataan ekspresi terdiri dari (opsional) ekspresi diikuti dengan titik koma. Tidak setiap ekspresi menghasilkan nilai; ekspresi tipe voidtidak memiliki nilai.
Keith Thompson
2

Pernyataan tanpa efek diizinkan karena akan lebih sulit untuk melarangnya daripada mengizinkannya. Ini lebih relevan ketika C pertama kali dirancang dan kompiler lebih kecil dan lebih sederhana.

Sebuah pernyataan ekspresi terdiri dari sebuah ekspresi yang diikuti dengan titik koma. Perilakunya adalah untuk mengevaluasi ekspresi dan membuang hasilnya (jika ada). Biasanya tujuannya adalah bahwa evaluasi ekspresi memiliki efek samping, tetapi tidak selalu mudah atau bahkan mungkin untuk menentukan apakah ekspresi yang diberikan memiliki efek samping.

Misalnya, panggilan fungsi adalah ekspresi, jadi panggilan fungsi diikuti oleh tanda titik koma adalah pernyataan. Apakah pernyataan ini memiliki efek samping?

some_function();

Tidak mungkin untuk mengatakan tanpa melihat implementasinya some_function.

Bagaimana dengan ini?

obj;

Mungkin tidak - tetapi jika objdidefinisikan sebagai volatile, maka itu terjadi.

Mengizinkan ekspresi apa pun dibuat menjadi ekspresi-ekspresi dengan menambahkan tanda titik koma membuat definisi bahasa lebih sederhana. Mewajibkan ekspresi memiliki efek samping akan menambah kerumitan definisi bahasa dan ke kompiler. C dibangun di atas seperangkat aturan yang konsisten (pemanggilan fungsi adalah ekspresi, penugasan adalah ekspresi, ekspresi yang diikuti oleh tanda titik koma adalah pernyataan) dan memungkinkan pemrogram melakukan apa yang mereka inginkan tanpa mencegah mereka melakukan hal-hal yang mungkin atau mungkin tidak masuk akal.

Keith Thompson
sumber
2

Pernyataan yang Anda daftarkan tanpa efek adalah contoh pernyataan ekspresi , yang sintaksnya diberikan di bagian 6.8.3p1 dari standar C sebagai berikut:

 ekspresi-pernyataan :
    ekspresi opt  ;

Semua bagian 6.5 didedikasikan untuk definisi ekspresi, tetapi secara longgar ekspresi terdiri dari konstanta dan pengidentifikasi yang terkait dengan operator. Khususnya, suatu ekspresi mungkin atau mungkin tidak mengandung operator penugasan dan mungkin atau mungkin tidak mengandung panggilan fungsi.

Jadi setiap ekspresi yang diikuti oleh tanda titik koma memenuhi syarat sebagai pernyataan ekspresi. Bahkan, setiap baris ini dari kode Anda adalah contoh pernyataan ekspresi:

i = i + 2;
5;
i;
printf("i: %d\n", i);

Beberapa operator berisi efek samping seperti seperangkat operator penugasan dan operator kenaikan / penurunan pra / pasca, dan operator panggilan fungsi () mungkin memiliki efek samping tergantung pada fungsi yang dimaksud. Namun tidak ada persyaratan bahwa salah satu operator harus memiliki efek samping.

Ini contoh lain:

atoi("1");

Ini memanggil fungsi dan membuang hasilnya, sama seperti panggilan printfdalam contoh Anda tetapi tidak seperti printffungsi panggil itu sendiri tidak memiliki efek samping.

dbush
sumber
1

Terkadang pernyataan seperti itu sangat berguna:

int foo(int x, int y, int z)
{
    (void)y;   //prevents warning
    (void)z;

    return x*x;
}

Atau ketika manual referensi memberitahu kita untuk hanya membaca register untuk mengarsipkan sesuatu - misalnya untuk menghapus atau mengatur beberapa flag (situasi yang sangat umum di dunia UC)

#define SREG   ((volatile uint32_t *)0x4000000)
#define DREG   ((volatile uint32_t *)0x4004000)

void readSREG(void)
{
    *SREG;   //we read it here
    *DREG;   // and here
}

https://godbolt.org/z/6wjh_5

P__J__
sumber
Ketika *SREGvolatile, *SREG;tidak memiliki efek pada model yang ditentukan oleh standar C. Standar C menyatakan bahwa ia memiliki efek samping yang dapat diamati.
Eric Postpischil
@EricPostpischil - tidak itu tidak memiliki efek yang dapat diamati , tetapi jika memiliki efek. Tak satu pun dari objek yang terlihat C telah berubah.
P__J__
C 2018 5.1.2.3 6 mendefinisikan perilaku yang dapat diamati dari program untuk memasukkan bahwa "Akses ke objek yang mudah menguap dievaluasi secara ketat sesuai dengan aturan mesin abstrak." Tidak ada pertanyaan tentang interpretasi atau deduksi; ini adalah definisi perilaku yang dapat diamati.
Eric Postpischil