Bagaimana membandingkan string dalam arahan preprocessor bersyarat C

92

Saya harus melakukan sesuatu seperti ini di C. Ini berfungsi hanya jika saya menggunakan char, tetapi saya membutuhkan string. Bagaimana saya bisa melakukan ini?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif
frx08
sumber
Mengapa Anda tidak bisa menggunakan strcmp saja?
@ Brian: Ya, saya membaca pertanyaannya juga :-). Hanya ingin memastikan dia tahu strcmp ada, dan tanggapannya mungkin mencerahkan, karena saya tidak bisa memikirkan alasan untuk melakukan hal #define ini.
2
Hanya ingin menyebutkan bahwa hal yang sama juga berlaku untuk kode biasa, bukan hanya preprocessor. Jangan pernah menggunakan string ketika nilai sederhana sudah cukup. String memiliki lebih banyak overhead daripada integer atau enum dan jika Anda tidak perlu melakukan apa pun selain membandingkannya, string adalah solusi yang salah.
swestrup
Akan berguna jika pertanyaan tersebut menyertakan lebih banyak informasi tentang perilaku yang diinginkan vs. perilaku aktual.
Brent Bradburn

Jawaban:

71

Saya tidak berpikir ada cara untuk melakukan perbandingan string panjang variabel sepenuhnya dalam arahan preprocessor. Anda mungkin bisa melakukan hal berikut ini:

#define USER_JACK 1
#define USER_QUEEN 2

#define USER USER_JACK 

#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif

Atau Anda dapat mengubah sedikit kode dan menggunakan kode C.

Brian R. Bondy
sumber
3
Atau dia bisa #define USER_VS (3 - USER)dalam kasus khusus ini. :)
Jesse Chisholm
17

[PEMBARUAN: 2018.05.03]

PERHATIAN : Tidak semua kompiler mengimplementasikan spesifikasi C ++ 11 dengan cara yang sama. Kode di bawah ini berfungsi di kompiler yang saya uji, sementara banyak pemberi komentar menggunakan kompilator yang berbeda.

Mengutip dari jawaban Shafik Yaghmour pada: Menghitung panjang string C pada waktu kompilasi. Apakah ini benar-benar sebuah constexpr?

Ekspresi konstan tidak dijamin akan dievaluasi pada waktu kompilasi, kami hanya memiliki kutipan non-normatif dari draf standar C ++ bagian 5.19 Ekspresi konstan yang mengatakan ini:

[...]> [Catatan: Ekspresi konstan dapat dievaluasi selama penerjemahan. — catatan akhir]

Kata itu canmembuat semua perbedaan di dunia.

Jadi, YMMV pada jawaban ini (atau apapun) yang melibatkan constexpr, tergantung pada interpretasi penulis kompilator terhadap spesifikasi.

[DIPERBARUI 2016.01.31]

Karena beberapa tidak menyukai jawaban saya sebelumnya karena menghindari seluruh compile time string compareaspek OP dengan mencapai tujuan tanpa perlu membandingkan string, berikut adalah jawaban yang lebih rinci.

Tidak boleh! Tidak di C98 atau C99. Bahkan tidak di C11. Tidak ada manipulasi MAKRO yang akan mengubah ini.

Definisi yang const-expressiondigunakan di #iftidak mengizinkan string.

Itu memungkinkan karakter, jadi jika Anda membatasi diri Anda pada karakter Anda dapat menggunakan ini:

#define JACK 'J'
#define QUEEN 'Q'

#define CHOICE JACK     // or QUEEN, your choice

#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

Kamu bisa! Di C ++ 11. Jika Anda mendefinisikan fungsi pembantu waktu kompilasi untuk perbandingan.

// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
    return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
        :  (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
        : c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])

#define JACK "jack"
#define QUEEN "queen"

#define USER JACK       // or QUEEN, your choice

#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif

#pragma message "USER    IS " USER
#pragma message "USER_VS IS " USER_VS

Jadi, pada akhirnya, Anda harus mengubah cara Anda mencapai tujuan Anda dalam memilih nilai string akhir untuk USERdan USER_VS.

Anda tidak dapat melakukan kompilasi string waktu membandingkan di C99, tetapi Anda dapat melakukan waktu kompilasi untuk memilih string.

Jika Anda benar-benar harus melakukan perbandingan sengatan waktu kompilasi, maka Anda perlu mengubah ke C ++ 11 atau varian yang lebih baru yang memungkinkan fitur itu.

[ASLI JAWABAN IKUTI]

Mencoba:

#define jack_VS queen
#define queen_VS jack

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS

// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U

PEMBARUAN: Penempelan token ANSI terkadang kurang jelas. ;-D

Menempatkan satu #sebelum makro menyebabkannya berubah menjadi string nilainya, bukan nilai kosongnya.

Menempatkan tanda ganda di ##antara dua token menyebabkan keduanya digabungkan menjadi satu token.

Jadi, makro USER_VSmemiliki perluasan jack_VSatau queen_VS, bergantung pada cara Anda mengatur USER.

The stringify makro S(...)menggunakan tipuan makro sehingga nilai makro bernama akan dikonversi menjadi string. alih-alih nama makro.

Jadi USER##_VSmenjadi jack_VS(atau queen_VS), tergantung bagaimana Anda mengatur USER.

Kemudian, ketika makro stringify digunakan sebagai S(USER_VS)nilai USER_VS( jack_VSdalam contoh ini) diteruskan ke langkah tipuan S_(jack_VS)yang mengubah nilainya ( queen) menjadi string "queen".

Jika Anda set USERke queenmaka hasil akhirnya adalah string "jack".

Untuk penggabungan token, lihat: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Untuk konversi string token, lihat: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[DIPERBARUI 2015.02.15 untuk memperbaiki kesalahan ketik.]

Jesse Chisholm
sumber
5
@JesseChisholm, sudah cek versi C ++ 11? Saya tidak dapat membuatnya berfungsi di GCC 4.8.1, 4.9.1, 5.3.0. Tercantum {{operator biner hilang sebelum token "("}} di {{#if 0 == c_strmp / * di sini * / (USER, QUEEN)}}
Dmitriy Elisov
3
@JesseChisholm Jadi saya berhasil mengkompilasi contoh C ++ 11 Anda jika saya mengubah #if 0 == c_strcmp( USER, JACK )keconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov
4
@JesseChisholm, hmm, masih belum berhasil. Variabel constexpr sama dengan nol di #if. Contoh Anda hanya berfungsi karena USER adalah JACK. Jika PENGGUNA adalah RATU, maka akan USER IS QUEENUSER_VS IS QUEEN
tertulis
9
Bagian c ++ 11 dari jawaban ini salah. Anda tidak dapat memanggil fungsi (bahkan constexpr) dari arahan preprocessor.
interjay
8
Jawaban yang benar-benar salah ini telah menyesatkan seseorang yang mereferensikannya. Anda tidak bisa memanggil fungsi constexpr dari preprocessor; constexpr bahkan tidak dikenali sebagai kata kunci sampai tahap terjemahan 7. Pemrosesan awal dilakukan dalam tahap terjemahan 4.
H Walters
10

Berikut ini bekerja untuk saya dengan dentang. Mengizinkan apa yang muncul sebagai perbandingan nilai makro simbolis. #error xxx hanya untuk melihat apa yang sebenarnya dilakukan oleh kompilator. Mengganti definisi cat dengan #define cat (a, b) a ## b merusak sesuatu.

#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__

#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)

#define USER jack // jack or queen

#if USER_VAL==xUSER_jack
  #error USER=jack
  #define USER_VS "queen"
#elif USER_VAL==xUSER_queen
  #error USER=queen
  #define USER_VS "jack"
#endif
Konstantin Bobrovskii
sumber
Tidak yakin apakah ini jahat, brilian, atau keduanya, tetapi itulah yang saya cari - terima kasih! Satu lagi trik berguna adalah #menentukan makro xUSER_ Anda mulai dari 1. Kemudian Anda dapat menambahkan klausa #else di akhir daftar #elsif untuk menangkap kasus ketika USER secara tidak sengaja disetel ke sesuatu yang tidak Anda ketahui cara menanganinya. (Jika tidak, jika Anda menomori dari 0 maka kasus 0 menjadi catchall Anda, karena itulah nilai numerik default preprocessor untuk simbol yang tidak ditentukan.)
sclamage
8

Gunakan nilai numerik sebagai ganti string.

Terakhir untuk mengubah konstanta JACK atau QUEEN menjadi string, gunakan operator stringize (dan / atau tokenisasi).

Patrick
sumber
2

Seperti yang telah disebutkan di atas, preprocessor ISO-C11 tidak mendukung perbandingan string. Namun, masalah penetapan makro dengan "nilai berlawanan" dapat diselesaikan dengan "penempelan token" dan "akses tabel". Solusi makro concatenate / stringify Jesse yang sederhana gagal dengan gcc 5.4.0 karena stringisasi dilakukan sebelum evaluasi penggabungan (sesuai dengan ISO C11). Namun, itu bisa diperbaiki:

#define P_(user) user ## _VS
#define VS(user) P_ (user)
#define S(U) S_(U)
#define S_(U) #U

#define jack_VS  queen
#define queen_VS jack

S (VS (jack))
S (jack)
S (VS (queen))
S (queen)

#define USER jack          // jack    or queen, your choice
#define USER_VS USER##_VS  // jack_VS or queen_VS
S (USER)
S (USER_VS)

Baris pertama (makro P_()) menambahkan satu tipuan untuk membiarkan baris berikutnya (makro VS()) menyelesaikan penggabungan sebelum stringisasi (lihat Mengapa saya memerlukan lapisan ganda dari tipuan untuk makro? ). Makro stringisasi ( S()dan S_()) berasal dari Jesse.

Tabel (makro jack_VSdanqueen_VS ) yang jauh lebih mudah dipelihara daripada konstruksi OP jika-maka-lain berasal dari Jesse.

Terakhir, blok empat baris berikutnya memanggil makro gaya fungsi. Blok empat baris terakhir adalah dari jawaban Jesse.

Menyimpan kode foo.cdan menjalankan preprocessor gcc -nostdinc -E foo.cmenghasilkan:

# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"



"jack"
"USER_VS"

Outputnya seperti yang diharapkan. Baris terakhir menunjukkan bahwa USER_VSmakro tidak diperluas sebelum stringisasi.

hermannk.dll
sumber
Ini bekerja dengan baik, sampai saya mencoba untuk benar-benar membandingkan string yang dihasilkan, untuk melakukan kompilasi bersyarat: #if (S(USER)=="jack")- Saya mendapatkan kesalahan preprocessor saat menggunakan "- error: invalid token at start of a preprocessor expression.
ysap
1

Jika string Anda adalah konstanta waktu kompilasi (seperti dalam kasus Anda), Anda dapat menggunakan trik berikut:

#define USER_JACK strcmp(USER, "jack")
#define USER_QUEEN strcmp(USER, "queen")
#if $USER_JACK == 0
#define USER_VS USER_QUEEN
#elif USER_QUEEN == 0
#define USER_VS USER_JACK
#endif

Kompilator dapat mengetahui hasil strcmp sebelumnya dan akan mengganti strcmp dengan hasilnya, sehingga memberi Anda #define yang dapat dibandingkan dengan arahan preprocessor. Saya tidak tahu apakah ada perbedaan antara kompiler / ketergantungan pada opsi kompilator, tetapi ini berhasil untuk saya di GCC 4.7.2.

EDIT: setelah penyelidikan lebih lanjut, sepertinya ini adalah ekstensi toolchain, bukan ekstensi GCC, jadi pertimbangkan itu ...

EpsilonVector
sumber
7
Ini tentunya bukan C standar, dan saya tidak melihat bagaimana ini akan bekerja dengan kompiler manapun. Kompilator terkadang dapat memberi tahu hasil ekspresi (bahkan pemanggilan fungsi, jika mereka sebaris), tetapi tidak pada pra-prosesor. Apakah Anda menggunakan $semacam ekstensi pra-prosesor?
ugoren
3
Sepertinya sintaks '#if $ USER_JACK == 0' berfungsi, setidaknya dengan GNU C ++ yang digunakan untuk membuat kode Android asli (JNI) ... Saya tidak tahu ini, tetapi ini sangat berguna, terima kasih telah memberi tahu kami tentang Itu!
gregko
6
Saya mencoba ini di GCC 4.9.1, & saya tidak percaya ini akan melakukan apa yang Anda pikirkan. Meskipun kode akan dikompilasi, itu tidak akan memberi Anda hasil yang diharapkan. '$' diperlakukan sebagai nama variabel. Jadi preprocessor mencari variabel '$ USER_JACK', tidak menemukannya & memberikan nilai default 0. Jadi, Anda akan selalu memiliki USER_VS yang didefinisikan sebagai USER_QUEEN terlepas dari strcmp
Vitali
1

Jawaban dari Patrick dan oleh Jesse Chisholm membuat saya melakukan hal berikut:

#define QUEEN 'Q'
#define JACK 'J'

#define CHECK_QUEEN(s) (s==QUEEN)
#define CHECK_JACK(s) (s==JACK)

#define USER 'Q'

[... later on in code ...]

#if CHECK_QUEEN(USER)
  compile_queen_func();
#elif CHECK_JACK(USER)
  compile_jack_func();
#elif
#error "unknown user"
#endif

Dari pada #define USER 'Q' #define USER QUEEN juga harus berfungsi tetapi tidak diuji juga berfungsi dan mungkin lebih mudah ditangani.

EDIT: Menurut komentar @ Jean-François Fabre, saya menyesuaikan jawaban saya.

benni
sumber
ubah (s==QUEEN?1:0)dengan (s==QUEEN)Anda tidak perlu terner, hasilnya sudah boolean
Jean-François Fabre
0
#define USER_IS(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9)\
ch0==c0 && ch1==c1 && ch2==c2 && ch3==c3 && ch4==c4 && ch5==c5 && ch6==c6 && ch7==c7 ;

#define ch0 'j'
#define ch1 'a'
#define ch2 'c'
#define ch3 'k'

#if USER_IS('j','a','c','k',0,0,0,0)
#define USER_VS "queen"
#elif USER_IS('q','u','e','e','n',0,0,0)
#define USER_VS "jack"
#endif

itu pada dasarnya array karakter statis dengan panjang tetap diinisialisasi secara manual daripada panjang variabel array karakter statis diinisialisasi secara otomatis selalu diakhiri dengan penghentian char null

yan bellavance
sumber
0

Anda tidak dapat melakukannya jika USER didefinisikan sebagai string yang dikutip.

Tetapi Anda dapat melakukannya jika PENGGUNA hanyalah JACK atau QUEEN atau Joker atau apapun.

Ada dua trik untuk digunakan:

  1. Token-splicing, di mana Anda menggabungkan pengenal dengan pengenal lain hanya dengan menggabungkan karakternya. Ini memungkinkan Anda membandingkan dengan JACK tanpa harus #define JACKmelakukan sesuatu
  2. ekspansi makro variadic, yang memungkinkan Anda menangani makro dengan jumlah variabel argumen. Ini memungkinkan Anda untuk memperluas pengenal tertentu menjadi berbagai jumlah koma, yang akan menjadi perbandingan string Anda.

Jadi mari kita mulai dengan:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)

Sekarang, jika saya menulis JACK_QUEEN_OTHER(USER), dan USER adalah JACK, preprocessor mengubahnya menjadiEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

Langkah kedua adalah penggabungan:

#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)

Sekarang JACK_QUEEN_OTHER(USER)menjadiEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Ini memberi kesempatan untuk menambahkan sejumlah koma sesuai dengan apakah string cocok atau tidak:

#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

Jika USER adalah JACK, JACK_QUEEN_OTHER(USER)menjadiEXPANSION2(x,x,x, 1, 2, 3)

Jika USER adalah QUEEN, JACK_QUEEN_OTHER(USER)menjadiEXPANSION2(x,x, 1, 2, 3)

Jika USER lain, JACK_QUEEN_OTHER(USER)menjadiEXPANSION2(ReSeRvEd_other, 1, 2, 3)

Pada titik ini, sesuatu yang kritis telah terjadi: argumen keempat pada makro EXPANSION2 adalah 1, 2, atau 3, bergantung pada apakah argumen asli yang diberikan adalah jack, queen, atau lainnya. Jadi yang harus kita lakukan adalah memilihnya. Untuk alasan yang bertele-tele, kita membutuhkan dua makro untuk langkah terakhir; mereka akan menjadi EXPANSION2 dan EXPANSION3, meskipun tampaknya tidak diperlukan.

Menggabungkan semuanya, kami memiliki 6 makro ini:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
#define EXPANSION2(a, b, c, d, ...) EXPANSION3(a, b, c, d)
#define EXPANSION3(a, b, c, d, ...) d
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

Dan Anda mungkin menggunakannya seperti ini:

int main() {
#if JACK_QUEEN_OTHER(USER) == 1
  printf("Hello, Jack!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 2
  printf("Hello, Queen!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 3
  printf("Hello, who are you?\n");
#endif
}

Tautan godbolt wajib: https://godbolt.org/z/8WGa19

jorgbrown
sumber
-5

Sederhana saja, saya pikir Anda bisa mengatakannya

#define NAME JACK    
#if NAME == queen 
pabitra nayak
sumber