Apakah algoritma strcasecmp cacat?

34

Saya mencoba menerapkan kembali strcasecmp fungsi dalam C dan saya perhatikan apa yang tampak sebagai inkonsistensi dalam proses perbandingan.

Dari man strcmp

Fungsi strcmp () membandingkan dua string s1 dan s2. Lokal tidak diperhitungkan (untuk perbandingan sadar-lokal, lihat strcoll (3)). Ini mengembalikan bilangan bulat kurang dari, sama dengan, atau lebih besar dari nol jika s1 ditemukan, masing-masing, lebih kecil dari, untuk mencocokkan, atau lebih besar dari s2.

Dari man strcasecmp

Fungsi strcasecmp () melakukan perbandingan byte-by-byte dari string s1 dan s2, mengabaikan case dari karakter. Ini mengembalikan bilangan bulat kurang dari, sama dengan, atau lebih besar dari nol jika s1 ditemukan, masing-masing, lebih kecil dari, untuk mencocokkan, atau lebih besar dari s2.

int strcmp(const char *s1, const char *s2);
int strcasecmp(const char *s1, const char *s2);

Mengingat, informasi ini, saya tidak mengerti hasil dari kode berikut:

#include <stdio.h>
#include <string.h>

int main()
{
    // ASCII values
    // 'A' = 65
    // '_' = 95
    // 'a' = 97

    printf("%i\n", strcmp("A", "_"));
    printf("%i\n", strcmp("a", "_"));
    printf("%i\n", strcasecmp("A", "_"));
    printf("%i\n", strcasecmp("a", "_"));
    return 0;
}

Ouput:

-1  # "A" is less than "_"
1   # "a" is more than "_"
2   # "A" is more than "_" with strcasecmp ???
2   # "a" is more than "_" with strcasecmp

Tampaknya, jika karakter saat s1ini dalam huruf, itu selalu dikonversi menjadi huruf kecil, terlepas dari apakah karakter saat ini dis2 ini dalam huruf atau tidak.

Adakah yang bisa menjelaskan perilaku ini? Bukankah seharusnya baris pertama dan ketiga identik?

Terima kasih sebelumnya!

PS:
Saya gcc 9.2.0pakai di Manjaro.
Juga, ketika saya mengkompilasi dengan -fno-builtinbendera yang saya dapatkan sebagai gantinya:

-30
2
2
2

Saya kira itu karena program tidak menggunakan fungsi gcc yang dioptimalkan, tetapi pertanyaannya tetap.

Haltarys
sumber
2
Tambahkan test case lain ke perangkat Anda: printf("%i\n", strcasecmp("a", "_"));Ini mungkin seharusnya memiliki hasil yang sama dengan printf("%i\n", strcasecmp("A", "_"));Tapi itu berarti bahwa salah satu dari dua panggilan case-sensitive ini akan tidak setuju dengan rekannya yang case-sensitive.
anton.burger
Tampaknya uraian yang strcasecmpAnda maksud tidak akurat. Detail lebih lanjut dalam jawaban yang dipilih.
Jabberwocky
9
Satu-satunya hal yang masuk akal. Fungsi yang mengatakan A < _ && a > _ && A == aakan menyebabkan banyak masalah.
ikegami
Selain itu: "Saya mencoba untuk mengimplementasikan kembali fungsi strcasecmp di C" -> Meskipun kode tidak ditampilkan, pastikan untuk membandingkan "seolah-olah" unsigned char. C17 / 18 "Penanganan string <string.h>" -> "Untuk semua fungsi dalam sub-ayat ini, setiap karakter harus ditafsirkan seolah-olah memiliki tipe unsigned char". Ini membuat perbedaan begitu charnilai berada di luar rentang ASCII 0-127.
chux
1
Pada perbedaan output dengan built-in dan tanpa: Keduanya mengatakan hal yang sama, karena hasilnya identik <0 dan> 0, dan Anda tidak memiliki contoh untuk == 0. Tetapi Anda dapat melihat algoritma bersinar melalui: beberapa nilai yang dikembalikan adalah perbedaan karakter pertama yang tidak sama.
the busybee

Jawaban:

31

Perilaku itu benar.

Per spesifikasi POSIXstr\[n\]casecmp() :

Ketika LC_CTYPEkategori lokal yang digunakan adalah dari lokal POSIX, fungsi-fungsi ini akan berperilaku seolah-olah string telah dikonversi menjadi huruf kecil dan kemudian perbandingan byte dilakukan. Kalau tidak, hasilnya tidak ditentukan.

Itu juga bagian dari bagian CATATAN pada halaman manual Linux :

Standar POSIX.1-2008 mengatakan tentang fungsi-fungsi ini:

Ketika kategori LC_CTYPE lokal yang digunakan berasal dari lokal POSIX, fungsi-fungsi ini akan berperilaku seolah-olah string telah dikonversi menjadi huruf kecil dan kemudian dilakukan perbandingan byte. Kalau tidak, hasilnya tidak ditentukan.

Mengapa?

Seperti @HansOlsson tunjukkan dalam jawabannya , melakukan perbandingan case-insensitive antara hanya huruf dan memungkinkan semua perbandingan lainnya memiliki hasil "alami" seperti yang dilakukan padastrcmp() akan memecah penyortiran.

Jika 'A' == 'a'(definisi perbandingan case-insensitive) kemudian '_' > 'A'dan '_' < 'a'(hasil "alami" dalam rangkaian karakter ASCII) tidak bisa keduanya benar.

Andrew Henle
sumber
Melakukan perbandingan tidak peka huruf besar-kecil antara huruf saja tidak akan menghasilkan '_' > 'A' && '_' < 'a'; sepertinya bukan contoh terbaik.
Asteroid Dengan Sayap
1
@AsteroidsWithWings Itulah karakter yang digunakan dalam pertanyaan. Dan jika 'a' == 'A' menurut definisi , jika Anda melakukan perbandingan antara nilai-nilai "alami" dari 'a',, 'A'dan '_', Anda tidak dapat melakukan perbandingan case-insensitive antara 'A'dan 'a'untuk mendapatkan kesetaraan dan mendapatkan hasil sortir yang konsisten.
Andrew Henle
Saya tidak membantah hal itu, tetapi contoh tandingan khusus yang Anda berikan tampaknya tidak relevan.
Asteroid Dengan Sayap
@AsteroidsWithWings Ikuti latihan mental membangun pohon biner dari 'a',, 'A'dan '_', melalui semua 6 perintah penyisipan ke dalam pohon, dan membandingkan hasil dari "huruf kecil selalu" seperti yang ditentukan untuk pertanyaan yang diajukan "hanya mengkonversi huruf ketika itu perbandingan surat ke surat ". Sebagai contoh, menggunakan algoritma yang terakhir dan mulai dengan '_', 'a'dan 'A'berakhir di sisi berlawanan dari pohon namun mereka didefinisikan sebagai sama. Algoritma "hanya mengonversi huruf menjadi huruf kecil dalam perbandingan huruf-huruf" rusak dan 3 karakter tersebut menunjukkan hal itu.
Andrew Henle
Oke, maka saya sarankan untuk menunjukkan itu dalam jawaban karena saat ini hanya melompat untuk menunjukkan bahwa " '_' > 'A' dan '_' < 'a'tidak bisa keduanya benar" tanpa memberi tahu kita mengapa kita berpikir itu akan terjadi. (Itu tugas penjawab, bukan untuk satu dari jutaan pembaca.)
Asteroids With Wings
21

Tautan lain, http://man7.org/linux/man-pages/man3/strcasecmp.3p.html untuk strcasecmp mengatakan bahwa mengonversi ke huruf kecil adalah perilaku yang benar (setidaknya di lokal POSIX).

Alasan untuk perilaku itu adalah bahwa jika Anda menggunakan strcasecmp untuk mengurutkan array string diperlukan untuk mendapatkan hasil yang masuk akal.

Kalau tidak, jika Anda mencoba mengurutkan "A", "C", "_", "b" menggunakan misalnya, qsort hasilnya akan tergantung pada urutan perbandingan.

Hans Olsson
sumber
3
Kalau tidak, jika Anda mencoba mengurutkan "A", "C", "_", "b" menggunakan misalnya, qsort hasilnya akan tergantung pada urutan perbandingan. Poin bagus. Itu kemungkinan alasan POSIX menentukan perilaku.
Andrew Henle
6
Lebih konkretnya, Anda membutuhkan urutan total untuk menyortir, yang tidak akan menjadi masalah jika Anda mendefinisikan perbandingan seperti dalam pertanyaan (karena tidak akan transitif).
Dukeling
8

Tampaknya, jika karakter saat ini di s1 adalah huruf, itu selalu dikonversi menjadi huruf kecil, terlepas dari apakah karakter saat ini di s2 adalah huruf atau tidak.

Itu benar - dan itulah yang harus dilakukan strcasecmp()fungsi ! Ini adalah fungsi, bukan bagian dari Standar tetapi, dari " Spesifikasi Basis Grup Terbuka, Edisi 6 ":POSIXC

Di lokal POSIX, strcasecmp () dan strncasecmp () akan berperilaku seolah-olah string telah dikonversi menjadi huruf kecil dan kemudian dilakukan perbandingan byte. Hasilnya tidak ditentukan di lokal lain.

Kebetulan, perilaku ini juga berkaitan dengan _stricmp()fungsi (seperti yang digunakan dalam Visual Studio / MSCV):

Fungsi _stricmp secara normal membandingkan string1 dan string2 setelah mengubah setiap karakter menjadi huruf kecil, dan mengembalikan nilai yang menunjukkan hubungan mereka.

Adrian Mole
sumber
2

ASCII kode desimal untuk Aadalah 65untuk _adalah 95dan untuk aini 97, jadi strcmp()itu melakukan apa itu rasa untuk melakukan. Berbicara leksikografis _lebih kecil adan lebih besar dari A.

strcasecmp()akan dianggap Asebagai a*, dan karena alebih besar dari _output juga benar.

* Standar POSIX.1-2008 mengatakan fungsi-fungsi ini (strcasecmp () dan strncasecmp ()):

Ketika kategori LC_CTYPE lokal yang digunakan berasal dari lokal POSIX, fungsi-fungsi ini akan berperilaku seolah-olah string telah dikonversi menjadi huruf kecil dan kemudian dilakukan perbandingan byte. Kalau tidak, hasilnya tidak ditentukan.

Sumber: http://man7.org/linux/man-pages/man3/strcasecmp.3.html

anastaciu
sumber
3
Poin OP Aadalah "lebih besar" daripada _ketika membandingkan case-insensitive, dan bertanya-tanya mengapa hasilnya tidak sama seperti ketika membandingkan case-sensitive.
anton.burger
6
Pernyataan Since strcasecmp () `adalah case-sensitive yang akan menganggap A sebagai a` adalah deduksi yang tidak valid. Rutin tidak peka huruf besar-kecil dapat memperlakukan semua huruf besar seolah-olah mereka huruf kecil, bisa memperlakukan semua huruf kecil seolah-olah mereka huruf besar, atau bisa memperlakukan setiap huruf besar sama dengan huruf kecil yang sesuai dan sebaliknya, tetapi masih membandingkannya ke karakter non-huruf dengan nilai mentahnya. Jawaban ini tidak menyatakan alasan untuk memilih salah satu dari kemungkinan-kemungkinan tersebut (alasan yang benar untuk mana dokumentasi mengatakan menggunakan huruf kecil).
Eric Postpischil
@EricPostpischil Standar POSIX.1-2008 mengatakan fungsi-fungsi ini (strcasecmp () dan strncasecmp ()): Ketika kategori LC_CTYPE lokal yang digunakan berasal dari lokal POSIX, fungsi-fungsi ini akan berperilaku seolah-olah string telah dikonversi menjadi huruf kecil dan kemudian perbandingan byte dilakukan. Kalau tidak, hasilnya tidak ditentukan.
anastaciu