Bagaimana Anda membandingkan struct untuk kesetaraan di C?

216

Bagaimana Anda membandingkan dua contoh struct untuk kesetaraan dalam standar C?

Hans Sjunnesson
sumber

Jawaban:

196

C tidak menyediakan fasilitas bahasa untuk melakukan ini - Anda harus melakukannya sendiri dan membandingkan setiap anggota struktur dengan anggota.

Greg Hewgill
sumber
19
jika variabel 2 struktur diinisialisasi dengan calloc atau mereka diatur dengan 0 oleh memset sehingga Anda dapat membandingkan 2 struktur Anda dengan memcmp dan tidak ada kekhawatiran tentang struktur sampah dan ini akan memungkinkan Anda untuk mendapatkan waktu
MOHAMED
21
@MOHAMED Membandingkan bidang floating point dengan 0.0, -0.0 NaNmasalah memcmp(). Pointer yang berbeda dalam representasi biner dapat menunjuk ke lokasi yang sama (misalnya DOS: seg: offset) dan sama. Beberapa sistem memiliki beberapa pointer nol yang membandingkan secara merata. Sama untuk yang tidak jelas intdengan -0 dan tipe floating point dengan pengkodean redundan. (Intel long double, decimal64, dll.) Masalah-masalah ini tidak membuat perbedaan calloc()digunakan atau tidak atau padding.
chux - Reinstate Monica
2
@ chux Pada sistem 32-atau 64-bit modern yang saya tahu, satu-satunya masalah adalah dengan floating point.
Demi
2
Jika Anda bertanya-tanya mengapa ==tidak bekerja dengan struktur (seperti saya), silakan lihat stackoverflow.com/questions/46995631/…
stefanct
4
@Emi: Hari ini. Perintah ke 10 untuk programmer C adalah 'Kamu harus melihat lebih dulu, meninggalkan, dan menolak bidat keji yang mengklaim bahwa "Seluruh dunia adalah VAX" ...'. Mengganti ini dengan "All a world a PC" bukanlah perbaikan.
Martin Bonner mendukung Monica
110

Anda mungkin tergoda untuk menggunakannya memcmp(&a, &b, sizeof(struct foo)), tetapi mungkin tidak berfungsi dalam semua situasi. Kompiler dapat menambahkan ruang penyejajaran penyelarasan ke struktur, dan nilai-nilai yang ditemukan di lokasi memori yang terletak di ruang buffer tidak dijamin menjadi nilai tertentu.

Tetapi, jika Anda menggunakan callocatau memsetukuran penuh dari struktur sebelum menggunakannya, Anda dapat melakukan perbandingan yang dangkalmemcmp (jika struktur Anda berisi pointer, itu hanya akan cocok jika alamat yang ditunjuk pointer adalah sama).

Sufian
sumber
19
Tutup, karena bekerja pada kompiler "hampir semua", tetapi tidak cukup. Lihat 6.2.1.6.4 di C90: "Dua nilai (selain NaNs) dengan representasi objek yang sama membandingkan sama, tetapi nilai yang membandingkan sama mungkin memiliki representasi objek yang berbeda."
Steve Jessop
22
Pertimbangkan bidang "BOOL". Dalam hal kesetaraan, setiap BOOL non-nol sama dengan setiap nilai BOOL non-nol. Jadi sementara 1 dan 2 keduanya BENAR dan karena itu sama, memcmp akan gagal.
ajs410
4
@ JSalazar Lebih mudah bagi Anda mungkin, tetapi jauh lebih sulit untuk kompiler dan CPU dan karenanya juga jauh lebih lambat. Menurut Anda mengapa kompiler menambahkan padding di tempat pertama? Tentu saja tidak menyia-nyiakan memori untuk apa-apa;)
Mecki
4
@ Demetri: misalnya nilai float positif dan negatif nol membandingkan sama pada setiap implementasi float IEEE, tetapi mereka tidak memiliki representasi objek yang sama. Jadi sebenarnya saya seharusnya tidak mengatakan itu bekerja pada "hampir semua kompiler", itu akan gagal pada implementasi yang memungkinkan Anda menyimpan nol negatif. Saya mungkin memikirkan representasi integer lucu pada saat saya membuat komentar.
Steve Jessop
4
@ Demetri: tetapi banyak yang mengandung float, dan si penanya bertanya "bagaimana Anda membandingkan struct", bukan "bagaimana Anda membandingkan struct yang tidak mengandung floats". Jawaban ini mengatakan Anda dapat melakukan perbandingan yang dangkal memcmpasalkan memori telah dihapus terlebih dahulu. Yang dekat dengan bekerja tetapi tidak benar. Ofc pertanyaannya juga tidak mendefinisikan "kesetaraan", jadi jika Anda menganggapnya "kesetaraan byte-bijaksana dari representasi objek" kemudian memcmpmelakukan hal itu (apakah memori dihapus atau tidak).
Steve Jessop
22

Jika Anda sering melakukannya saya sarankan menulis fungsi yang membandingkan dua struktur. Dengan begitu, jika Anda pernah mengubah struktur, Anda hanya perlu mengubah perbandingan di satu tempat.

Adapun cara melakukannya .... Anda harus membandingkan setiap elemen secara individual

Ben
sumber
1
Saya akan menulis fungsi terpisah bahkan jika saya menggunakannya hanya sekali.
Sam
18

Anda tidak dapat menggunakan memcmp untuk membandingkan struct untuk kesetaraan karena potensi karakter padding acak antara bidang dalam struct.

  // bad
  memcmp(&struct1, &struct2, sizeof(struct1));

Di atas akan gagal untuk struct seperti ini:

typedef struct Foo {
  char a;
  /* padding */
  double d;
  /* padding */
  char e;
  /* padding */
  int f;
} Foo ;

Anda harus menggunakan perbandingan anggota-bijaksana agar aman.


sumber
25
Tidak mungkin menjadi padding setelah double; char akan disejajarkan secara sempurna segera setelah double.
Jonathan Leffler
7

@Reg benar bahwa seseorang harus menulis fungsi perbandingan eksplisit dalam kasus umum.

Dimungkinkan untuk digunakan memcmpjika:

  • struct tidak mengandung bidang floating-point yang mungkin NaN.
  • struct tidak mengandung padding (digunakan -Wpaddeddengan dentang untuk memeriksa ini) ATAU struct secara eksplisit diinisialisasi dengan memsetsaat inisialisasi.
  • tidak ada tipe anggota (seperti Windows BOOL) yang memiliki nilai berbeda tetapi setara.

Kecuali Anda pemrograman untuk embedded system (atau menulis perpustakaan yang mungkin digunakan pada mereka), saya tidak akan khawatir tentang beberapa kasus sudut dalam standar C. Perbedaan pointer dekat vs jauh tidak ada pada perangkat 32- atau 64-bit. Tidak ada sistem non-embedded yang saya tahu memiliki banyak NULLpointer.

Pilihan lain adalah untuk menghasilkan fungsi kesetaraan secara otomatis. Jika Anda meletakkan definisi struct Anda dengan cara yang sederhana, dimungkinkan untuk menggunakan pemrosesan teks sederhana untuk menangani definisi struct yang sederhana. Anda dapat menggunakan libclang untuk case umum - karena menggunakan frontend yang sama dengan Clang, ia menangani semua case sudut dengan benar (bug pembatas).

Saya belum melihat perpustakaan pembuatan kode seperti itu. Namun, tampaknya relatif sederhana.

Namun, ini juga merupakan kasus dimana fungsi kesetaraan yang dihasilkan seperti itu sering melakukan hal yang salah pada level aplikasi. Misalnya, haruskah dua UNICODE_STRINGstruct di Windows dibandingkan secara dangkal atau dalam?

Demi
sumber
2
Secara eksplisit menginisialisasi struct dengan memset, dll. Tidak menjamin nilai bit padding setelah menulis lebih lanjut ke elemen struct, lihat: stackoverflow.com/q/52684192/689161
gengkev
4

Catatan Anda dapat menggunakan memcmp () pada struktur yang tidak statis tanpa khawatir tentang bantalan, selama Anda tidak menginisialisasi semua anggota (sekaligus). Ini didefinisikan oleh C90:

http://www.pixelbeat.org/programming/gcc/auto_init.html

pixelbeat
sumber
1
Apakah ini benar-benar ditentukan yang {0, }juga akan nol byte padding?
Alnitak
GCC setidaknya nol nol padding byte untuk struct yang diinisialisasi sebagian seperti yang ditunjukkan pada tautan di atas, dan detail stackoverflow.com/questions/13056364/... bahwa C11 menentukan perilaku itu.
pixelbeat
1
Tidak terlalu berguna secara umum, karena semua padding menjadi tidak ditentukan saat menugaskan anggota mana pun
MM
2

Itu tergantung pada apakah pertanyaan yang Anda ajukan adalah:

  1. Apakah kedua struct ini adalah objek yang sama?
  2. Apakah mereka memiliki nilai yang sama?

Untuk mengetahui apakah mereka adalah objek yang sama, bandingkan pointer ke dua struct untuk kesetaraan. Jika Anda ingin mencari tahu secara umum jika mereka memiliki nilai yang sama Anda harus melakukan perbandingan yang mendalam. Ini melibatkan membandingkan semua anggota. Jika anggotanya adalah penunjuk ke struct lain Anda juga perlu recurse ke struct tersebut juga.

Dalam kasus khusus di mana struct tidak mengandung pointer, Anda dapat melakukan memcmp untuk melakukan perbandingan bitwise dari data yang terkandung dalam masing-masing tanpa harus tahu apa artinya data.

Pastikan Anda tahu apa arti 'sama dengan' untuk setiap anggota - jelas untuk int tetapi lebih halus ketika datang ke nilai floating-point atau tipe yang ditentukan pengguna.

domgblackwell
sumber
2

memcmptidak membandingkan struktur, memcmpmembandingkan biner, dan selalu ada sampah di struct, oleh karena itu selalu keluar Palsu sebagai perbandingan.

Bandingkan elemen demi elemen yang aman dan tidak gagal.

sergio
sumber
1
jika variabel 2 struktur diinisialisasi dengan calloc atau mereka diatur dengan 0 oleh memset sehingga Anda dapat membandingkan 2 struktur Anda dengan memcmp dan tidak ada kekhawatiran tentang struktur sampah dan ini akan memungkinkan Anda untuk mendapatkan waktu
MOHAMED
1

Jika struct hanya berisi primitif atau jika Anda tertarik pada kesetaraan yang ketat maka Anda dapat melakukan sesuatu seperti ini:

int my_struct_cmp (const struct my_struct * lhs, const struct my_struct * rhs)
{
    kembalikan memcmp (lhs, rsh, sizeof (struct my_struct));
}

Namun, jika struct Anda berisi pointer ke struct atau serikat lain maka Anda harus menulis fungsi yang membandingkan primitif dengan benar dan membuat panggilan perbandingan terhadap struktur lain yang sesuai.

Perlu diketahui, bahwa Anda harus menggunakan memset (& a, sizeof (struct my_struct), 1) untuk nol rentang memori struktur sebagai bagian dari inisialisasi ADT Anda.

Kevin S.
sumber
-1

jika variabel 2 struktur diinisialisasi dengan calloc atau mereka diatur dengan 0 oleh memset sehingga Anda dapat membandingkan 2 struktur Anda dengan memcmp dan tidak ada kekhawatiran tentang struktur sampah dan ini akan memungkinkan Anda untuk mendapatkan waktu

MOHAMED
sumber
-2

Contoh patuh ini menggunakan ekstensi kompilator paket #pragma dari Microsoft Visual Studio untuk memastikan anggota struktur dikemas sekencang mungkin:

#include <string.h>

#pragma pack(push, 1)
struct s {
  char c;
  int i;
  char buffer[13];
};
#pragma pack(pop)

void compare(const struct s *left, const struct s *right) { 
  if (0 == memcmp(left, right, sizeof(struct s))) {
    /* ... */
  }
}
Hesham Eraqi
sumber
1
Itu memang benar. Tetapi dalam kebanyakan kasus Anda tidak ingin struct Anda harus dikemas! Cukup banyak instruksi, dan petunjuk, mengharuskan input data selaras kata. Jika tidak, maka kompiler perlu menambahkan instruksi tambahan untuk menyalin dan menyelaraskan kembali data sebelum instruksi yang sebenarnya dapat dieksekusi. Jika kompiler tidak akan menyelaraskan kembali data, CPU akan mengeluarkan pengecualian.
Ruud Althuizen