Mengapa #define TRUE (1 == 1) dalam makro Coolool alih-alih hanya 1?

160

Saya telah melihat definisi dalam C

#define TRUE (1==1)
#define FALSE (!TRUE)

Apakah ini perlu? Apa manfaatnya dengan mendefinisikan TRUE sebagai 1, dan SALAH sebagai 0?

Robert Harvey
sumber
35
Dan banyak lagi #define TRUE (’/’/’/’):; #define FALSE (’-’-’-’)(diambil dari coding-guidelines.com/cbook/cbook1_1.pdf halaman 871)
osgx
2
Tidak, ini paranoia oleh orang yang tidak tahu apa-apa. Dalam C, 1 dan 0 lakukan dalam semua keadaan yang sama.
Jens
@osgx Apa artinya itu?
mrgloom

Jawaban:

155

Pendekatan ini akan menggunakan booleantipe aktual (dan menyelesaikan ke truedan false) jika kompiler mendukungnya. (khususnya, C ++)

Namun, akan lebih baik untuk memeriksa apakah C ++ sedang digunakan (melalui __cplusplusmakro) dan benar-benar menggunakan truedan false.

Dalam kompiler C, ini sama dengan 0dan 1.
(perhatikan bahwa menghapus tanda kurung akan merusak karena urutan operasi)

Slaks
sumber
7
Itu salah, bools tidak digunakan di sini. Hasilnya 1==1adalah int. (lihat stackoverflow.com/questions/7687403/… .)
Mat
4
@ Mat: Bahkan di C ++, dengan booleantipe?
SLaks
9
Pertanyaan ditandai C, tetapi memang dalam C ++ operator relasional mengembalikan trueatau false.
Mat
5
@Mat: Saya menduga bahwa kode tersebut ditulis dalam header C untuk bermain baik dengan C ++
SLaks
20
@ SLaks Jika ingin bermain baik dengan C ++, itu akan #define TRUE truedan #define FALSE falsekapan pun __cplusplusdidefinisikan.
Nikos C.
137

Jawabannya adalah portabilitas. Nilai numerik TRUEdan FALSEtidak penting. Apa yang penting adalah bahwa pernyataan seperti if (1 < 2)mengevaluasi ke if (TRUE)dan pernyataan seperti if (1 > 2)mengevaluasi ke if (FALSE).

Diberikan, dalam C, (1 < 2)mengevaluasi 1dan (1 > 2)mengevaluasi 0, sehingga seperti yang dikatakan orang lain, tidak ada perbedaan praktis sejauh menyangkut kompiler. Tetapi dengan membiarkan kompiler mendefinisikan TRUEdan FALSEsesuai dengan aturannya sendiri, Anda membuat maknanya secara eksplisit untuk programmer, dan Anda menjamin konsistensi dalam program Anda dan pustaka lainnya (dengan asumsi pustaka lain mengikuti standar C ... Anda akan terkesima).


Some History
Beberapa BASIC didefinisikan FALSEsebagai 0dan TRUEsebagai -1. Seperti banyak bahasa modern, mereka menafsirkan nilai bukan nol TRUE, tetapi mereka mengevaluasi ekspresi boolean yang benar -1. NOTOperasi mereka dilaksanakan dengan menambahkan 1 dan membalik tanda, karena efisien untuk melakukannya dengan cara itu. Jadi 'TIDAK x' menjadi -(x+1). Efek samping dari hal ini adalah bahwa nilai suka 5dievaluasi TRUE, tetapi NOT 5dievaluasi -6, yang juga TRUE! Menemukan bug semacam ini tidak menyenangkan.

Praktik Terbaik
Mengingat de facto aturan yang nol ditafsirkan sebagai FALSEdan setiap nilai bukan nol ditafsirkan sebagai TRUE, Anda harus tidak pernah membandingkan ekspresi boolean-mencari untuk TRUEatauFALSE . Contoh:

if (thisValue == FALSE)  // Don't do this!
if (thatValue == TRUE)   // Or this!
if (otherValue != TRUE)  // Whatever you do, don't do this!

Mengapa? Karena banyak programmer menggunakan cara pintas memperlakukan ints sebagai bools. Mereka tidak sama, tetapi umumnya kompiler mengizinkannya. Jadi, misalnya, sangat sah untuk menulis

if (strcmp(yourString, myString) == TRUE)  // Wrong!!!

Itu terlihat sah, dan kompiler akan dengan senang hati menerimanya, tetapi mungkin tidak melakukan apa yang Anda inginkan. Itu karena nilai kembalinya strcmp()adalah

      0 jika yourString == myString
    <0 jika yourString < myString
    > 0 jikayourString > myString

Jadi baris di atas TRUEhanya mengembalikan ketika yourString > myString.

Cara yang tepat untuk melakukan ini adalah baik

// Valid, but still treats int as bool.
if (strcmp(yourString, myString))

atau

// Better: lingustically clear, compiler will optimize.
if (strcmp(yourString, myString) != 0)

Demikian pula:

if (someBoolValue == FALSE)     // Redundant.
if (!someBoolValue)             // Better.
return (x > 0) ? TRUE : FALSE;  // You're fired.
return (x > 0);                 // Simpler, clearer, correct.
if (ptr == NULL)                // Perfect: compares pointers.
if (!ptr)                       // Sleazy, but short and valid.
if (ptr == FALSE)               // Whatisthisidonteven.

Anda akan sering menemukan beberapa "contoh buruk" ini dalam kode produksi, dan banyak pemrogram berpengalaman bersumpah dengan mereka: mereka bekerja, ada yang lebih pendek daripada alternatif mereka yang benar (pedantically?), Dan idiom-idiom tersebut hampir dikenal secara universal. Tetapi pertimbangkan: versi "benar" tidak kurang efisien, mereka dijamin portabel, mereka akan lulus bahkan pada tingkat yang paling ketat, dan bahkan pemrogram baru akan memahaminya.

Bukankah itu sepadan?

Adam Liss
sumber
6
(1==1)tidak lebih portabel daripada 1. Aturan kompiler sendiri adalah dari bahasa C, yang jelas dan tidak ambigu tentang semantik kesetaraan dan operator relasional. Saya belum pernah melihat seorang kompiler membuat kesalahan ini.
Keith Thompson
1
Sebenarnya nilai yang dikembalikan strcmpdiketahui kurang dari, sama atau lebih besar dari 0. Tidak dijamin menjadi -1, 0 atau 1 dan ada platform di alam bebas yang tidak mengembalikan nilai-nilai itu untuk mendapatkan kecepatan implementasi. Jadi kalau strcmp(a, b) == TRUEbegitu a > btetapi implikasi sebaliknya mungkin tidak berlaku.
Maciej Piechotka
2
@KeithThompson - Mungkin "portabilitas" adalah istilah yang salah. Tetapi kenyataannya tetap bahwa (1 == 1) adalah nilai boolean; 1 tidak.
Adam Liss
2
@AdamLiss: Dalam C, (1==1)dan 1keduanya merupakan ekspresi tipe konstan intdengan nilai 1. Keduanya identik secara semantik. Saya kira Anda dapat menulis kode yang melayani pembaca yang tidak tahu itu, tetapi di mana itu berakhir?
Keith Thompson
2
'tidak' 5 adalah, memang, -6, pada level bit.
woliveirajr
51

The (1 == 1)Trik ini berguna untuk mendefinisikan TRUEdengan cara yang transparan untuk C, namun menyediakan mengetik yang lebih baik di C ++. Kode yang sama dapat ditafsirkan sebagai C atau C ++ jika Anda menulis dalam dialek yang disebut "Bersihkan C" (yang mengkompilasi sebagai C atau C ++) atau jika Anda menulis file header API yang dapat digunakan oleh programmer C atau C ++.

Dalam unit terjemahan C, 1 == 1memiliki arti yang sama persis seperti 1; dan 1 == 0memiliki arti yang sama dengan 0. Namun, dalam unit terjemahan C ++, 1 == 1memiliki tipe bool. Jadi TRUEmakro didefinisikan dengan cara itu mengintegrasikan lebih baik ke C ++.

Contoh bagaimana itu terintegrasi lebih baik adalah bahwa misalnya jika fungsi foomemiliki kelebihan untuk intdan untuk bool, maka foo(TRUE)akan memilih boolkelebihan. Jika TRUEhanya didefinisikan sebagai 1, maka itu tidak akan berfungsi dengan baik di C ++. foo(TRUE)akan menginginkan intkelebihan.

Tentu saja, C99 diperkenalkan bool, true, dan falsedan ini dapat digunakan di file header yang bekerja dengan C99 dan dengan C.

Namun:

  • praktik mendefinisikan TRUEdan FALSEsebagai (0==0)dan (1==0)sebelum C99.
  • masih ada alasan bagus untuk menjauh dari C99 dan bekerja dengan C90.

Jika Anda bekerja dalam proyek campuran C dan C ++, dan tidak ingin C99, tentukan huruf kecil true, falsedan boolsebagai gantinya.

#ifndef __cplusplus
typedef int bool;
#define true (0==0)
#define false (!true)
#endif

Yang sedang berkata, 0==0trik itu (adalah?) Digunakan oleh beberapa programmer bahkan dalam kode yang tidak pernah dimaksudkan untuk beroperasi dengan C ++ dengan cara apa pun. Itu tidak membeli apa pun dan menunjukkan bahwa programmer memiliki kesalahpahaman tentang cara kerja boolean di C.


Jika penjelasan C ++ tidak jelas, berikut ini adalah program uji:

#include <cstdio>

void foo(bool x)
{
   std::puts("bool");  
}

void foo(int x)
{
   std::puts("int");  
}

int main()
{
   foo(1 == 1);
   foo(1);
   return 0;
}

Hasil:

bool
int

Adapun pertanyaan dari komentar tentang bagaimana kelebihan fungsi C ++ relevan dengan pemrograman campuran C dan C ++. Ini hanya menggambarkan perbedaan tipe. Alasan yang valid untuk menginginkan truekonstanta boolketika dikompilasi sebagai C ++ adalah untuk diagnostik yang bersih. Pada tingkat peringatan tertinggi, kompiler C ++ mungkin memperingatkan kita tentang konversi jika kita melewatkan integer sebagai boolparameter. Salah satu alasan penulisan Clean C bukan hanya karena kode kami lebih portabel (karena dipahami oleh kompiler C ++, tidak hanya kompiler C), tetapi kita dapat mengambil manfaat dari pendapat diagnostik kompiler C ++.

Kaz
sumber
3
Sangat baik, dan kurang dihargai, jawabannya. Sama sekali tidak jelas bahwa dua definisi TRUEakan berbeda di bawah C ++.
user4815162342
4
Bagaimana fungsi kelebihan beban relevan dengan kode yang dikompilasi sebagai C dan C ++?
Keith Thompson
@KeithThompson Ini bukan hanya tentang overloading tapi juga mengetik secara umum, overloading adalah contoh paling praktis saat mulai digunakan. Tentu saja kode C ++ tanpa kelebihan, templat dan semua hal-hal yang "rumit" dihapus untuk "kompatibilitas-C" sama sekali tidak terlalu peduli tentang jenis, tetapi itu tidak berarti orang harus menggulingkan batasan tipe konseptual dalam bahasa tertentu .
Christian Rau
1
@ChristianRau: Apa maksudmu "tidak terlalu peduli dengan tipe"? Jenis adalah pusat dari bahasa C; setiap ekspresi, nilai, dan objek dalam program C memiliki tipe yang didefinisikan dengan baik. Jika Anda ingin mendefinisikan sesuatu secara berbeda dalam C dan C ++ (dalam kasus yang jarang terjadi di mana Anda benar-benar perlu menulis kode yang dikompilasi sebagai C dan C ++), Anda dapat menggunakan #ifdef __cplusplusuntuk mengekspresikan maksud Anda dengan lebih jelas.
Keith Thompson
@KeithThompson Ya saya tahu betapa pentingnya tipe. Hanya saja, tanpa semua hal yang diketahui tipe seperti kelebihan beban dan templat, hal-hal seperti perbedaan antara booldan inttidak terlalu penting dalam praktik, karena semuanya secara implisit dapat dikonversi satu sama lain (dan dalam C sebenarnya "sama" , catat tanda kutipnya. , meskipun) dan tidak ada banyak situasi di mana Anda benar-benar perlu memisahkan diri di antara keduanya. "tidak banyak" mungkin terlalu berat, "jauh lebih sedikit dibandingkan dengan kode yang menggunakan template dan kelebihan beban" mungkin lebih baik.
Christian Rau
18
#define TRUE (1==1)
#define FALSE (!TRUE)

setara dengan

#define TRUE  1
#define FALSE 0

dalam C.

Hasil dari operator relasional adalah 0atau 1. 1==1dijamin untuk dievaluasi 1dan !(1==1)dijamin untuk dievaluasi 0.

Sama sekali tidak ada alasan untuk menggunakan formulir pertama. Perhatikan bahwa bentuk pertama tidak kurang efisien karena pada hampir semua kompiler ekspresi konstan dievaluasi pada waktu kompilasi daripada pada saat run-time. Ini diizinkan menurut aturan ini:

(C99, 6.6p2) "Ekspresi konstan dapat dievaluasi selama penerjemahan daripada runtime, dan karenanya dapat digunakan di sembarang tempat yang mungkin berupa konstanta."

PC-Lint bahkan akan mengeluarkan pesan (506, nilai konstan boolean) jika Anda tidak menggunakan literal untuk TRUEdan FALSEmakro:

Untuk C, TRUEharus didefinisikan sebagai 1. Namun, bahasa lain menggunakan jumlah selain 1 sehingga beberapa programmer merasa !0bermain aman.

Juga di C99, stdbool.hdefinisi untuk makro boolean truedan false langsung menggunakan literal:

#define true   1
#define false  0
ouah
sumber
1
Saya ragu, BENAR diganti pada setiap penggunaan dengan 1 == 1, sementara hanya menggunakan 1 akan menggantikan 1, bukankah metode overhead perbandingan pertama metode ... atau dibuat kompiler yang dioptimalkan?
pinkpanther
4
@pinkpanther ekspresi konstan biasanya dievaluasi pada waktu kompilasi dan karenanya tidak menyebabkan overhead.
ouah
2
1==1dijamin akan dievaluasi ke1
ouah
3
@NikosC. itu semacam pertanyaan yang bagus. Ini penting untuk kode formulir if(foo == true), yang akan berubah dari sekadar praktik buruk menjadi buggy.
djechlin
1
+1 untuk menunjukkan bahaya di (x == TRUE)dapat memiliki nilai kebenaran yang berbeda dari x.
Joshua Taylor
12

Selain C ++ (sudah disebutkan), manfaat lain adalah untuk alat analisis statis. Kompiler akan menghapus semua inefisiensi, tetapi penganalisa statis dapat menggunakan tipe abstraknya sendiri untuk membedakan antara hasil perbandingan dan tipe integer lainnya, sehingga ia mengetahui secara implisit bahwa TRUE harus merupakan hasil perbandingan dan tidak boleh dianggap kompatibel dengan bilangan bulat.

Jelas C mengatakan bahwa mereka kompatibel, tetapi Anda dapat memilih untuk tidak sengaja menggunakan fitur itu untuk membantu menyoroti bug - misalnya, di mana seseorang mungkin bingung &dan &&, atau mereka telah mengacaukan prioritas operator mereka.

sh1
sumber
1
Itu poin yang baik, dan mungkin beberapa alat ini bahkan dapat menangkap kode konyol if (boolean_var == TRUE) dengan cara ekspansi if (boolean_var == (1 == 1))yang berkat info tipe ditingkatkan dari (1 == 1)node jatuh ke dalam pola if (<*> == <boolean_expr>).
Kaz
4

Perbedaan praktisnya tidak ada. 0dievaluasi ke falsedan 1dievaluasi ke true. Fakta bahwa Anda menggunakan ekspresi boolean ( 1 == 1) atau 1, untuk mendefinisikan true, tidak ada bedanya. Keduanya dievaluasi int.

Perhatikan bahwa C library standar menyediakan header khusus untuk mendefinisikan boolean: stdbool.h.

Sepatu
sumber
tentu saja Anda belum ... tetapi beberapa orang mungkin berpikir sebaliknya terutama untuk angka negatif itu sebabnya :)
pinkpanther
Apa? Anda memilikinya mundur. truedievaluasi ke 1dan falsedievaluasi ke 0. C tidak tahu tentang tipe boolean asli, mereka hanya int.
djechlin
Di C, operator relasional dan kesetaraan menghasilkan jenis int, dengan nilai 0atau 1. C memang memiliki tipe boolean yang sebenarnya ( _Bool, dengan makro yang booldidefinisikan <stdbool.h>, tetapi itu hanya ditambahkan dalam C99, yang tidak mengubah semantik operator untuk menggunakan tipe baru.
Keith Thompson
@djechlin: Pada standar tahun 1999, C memang memiliki tipe boolean asli. Itu disebut _Bool, dan <stdbool.h>telah #define bool _Bool.
Keith Thompson
@KeithThompson, Anda benar tentang 1 == 1yang dievaluasi sebagai int. Diedit.
Sepatu
3

Kami tidak tahu nilai pasti bahwa TRUE sama dengan dan kompiler dapat memiliki definisi sendiri. Jadi apa yang Anda privode adalah menggunakan definisi internal kompiler. Ini tidak selalu diperlukan jika Anda memiliki kebiasaan pemrograman yang baik tetapi dapat menghindari masalah untuk beberapa gaya pengkodean yang buruk, misalnya:

if ((a> b) == BENAR)

Ini bisa menjadi bencana jika Anda mendefinisikan TRUE secara manual sebagai 1, sedangkan nilai internal TRUE adalah nilai lain.

capiggue
sumber
Dalam C, >operator selalu menghasilkan 1 untuk true, 0 untuk false. Tidak ada kemungkinan kompiler C mendapatkan kesalahan ini. Perbandingan kesetaraan TRUEdan FALSEgaya yang buruk; di atas lebih jelas ditulis sebagai if (a > b). Tetapi gagasan bahwa penyusun C yang berbeda dapat memperlakukan kebenaran dan salah secara berbeda sama sekali salah.
Keith Thompson
2
  1. Daftar barang

Biasanya dalam Bahasa Pemrograman C, 1 didefinisikan sebagai benar dan 0 didefinisikan sebagai salah. Karenanya mengapa Anda sering melihat hal berikut:

#define TRUE 1 
#define FALSE 0

Namun, angka apa pun yang tidak sama dengan 0 akan dievaluasi menjadi benar juga dalam pernyataan bersyarat. Oleh karena itu dengan menggunakan di bawah ini:

#define TRUE (1==1)
#define FALSE (!TRUE)

Anda dapat secara eksplisit menunjukkan bahwa Anda mencoba untuk bermain aman dengan membuat kesalahan sama dengan apa pun yang tidak benar.

Sabashan Ragavan
sumber
4
Saya tidak akan menyebutnya "bermain aman" - sebaliknya, Anda memberi diri Anda rasa aman yang salah.
dodgethesteamroller