Làm thế nào để bạn so sánh các cấu trúc cho sự bình đẳng trong C?


216

Làm thế nào để bạn so sánh hai trường hợp của cấu trúc cho sự bình đẳng trong tiêu chuẩn C?

Câu trả lời:


196

C cung cấp không có phương tiện ngôn ngữ để làm điều này - bạn phải tự làm và so sánh từng thành viên cấu trúc theo thành viên.


19
nếu biến 2 cấu trúc được khởi tạo với calloc hoặc chúng được đặt bằng 0 theo memset để bạn có thể so sánh 2 cấu trúc của mình với memcmp và không phải lo lắng về rác cấu trúc và điều này sẽ cho phép bạn kiếm được thời gian
MOHAMED

21
@MOHAMED So sánh các trường dấu phẩy động 0.0, -0.0 NaNlà một vấn đề với memcmp(). Các con trỏ khác nhau trong biểu diễn nhị phân có thể trỏ đến cùng một vị trí (ví dụ: DOS: seg: offset) và do đó là bằng nhau. Một số hệ thống có nhiều con trỏ null so sánh như nhau. Tương tự đối với che khuất intvới loại -0 và dấu phẩy động với bảng mã dự phòng. (Intel long double, binary64, v.v.) Những vấn đề này không tạo ra sự khác biệt calloc()hay không sử dụng hoặc đệm.
chux - Phục hồi lại

2
@chux Trên bất kỳ hệ thống 32 hoặc 64 bit hiện đại nào tôi biết, vấn đề duy nhất là với dấu phẩy động.
Demi

2
Trong trường hợp bạn tự hỏi tại sao ==không hoạt động với các cấu trúc (như tôi), vui lòng xem stackoverflow.com/questions/46995631/
mẹo

4
@Demi: Hôm nay. Điều răn thứ 10 đối với các lập trình viên C là 'Ngươi sẽ bỏ mặc, từ bỏ và từ bỏ những dị giáo ghê tởm mà tuyên bố rằng tất cả thế giới là một VAX ... .... Thay thế điều này bằng "All the world a PC" không phải là một sự cải tiến.
Martin Bonner hỗ trợ Monica

110

Bạn có thể bị cám dỗ để sử dụng memcmp(&a, &b, sizeof(struct foo)), nhưng nó có thể không hoạt động trong mọi tình huống. Trình biên dịch có thể thêm không gian bộ đệm căn chỉnh vào một cấu trúc và các giá trị được tìm thấy tại các vị trí bộ nhớ nằm trong không gian bộ đệm không được đảm bảo là bất kỳ giá trị cụ thể nào.

Nhưng, nếu bạn sử dụng callochoặc memsetkích thước đầy đủ của các cấu trúc trước khi sử dụng chúng, bạn có thể làm một nông so với memcmp(nếu cấu trúc của bạn chứa con trỏ, nó sẽ phù hợp với chỉ nếu địa chỉ con trỏ đang trỏ tới đều giống nhau).


19
Đóng, bởi vì nó hoạt động trên trình biên dịch "gần như tất cả", nhưng không hoàn toàn. Kiểm tra 6.2.1.6.4 trong C90: "Hai giá trị (không phải NaN) có cùng biểu diễn đối tượng so sánh bằng nhau, nhưng các giá trị so sánh bằng nhau có thể có các biểu diễn đối tượng khác nhau."
Steve Jessop

22
Hãy xem xét một trường "BOOL". Về mặt bình đẳng, mọi BOOL khác không đều bằng với mọi giá trị BOOL khác không. Vì vậy, trong khi 1 và 2 có thể cả TRUE và do đó bằng nhau, memcmp sẽ thất bại.
ajs410

4
@JSalazar Dễ dàng hơn cho bạn, nhưng khó hơn nhiều cho trình biên dịch và CPU và do đó cũng chậm hơn nhiều. Tại sao bạn nghĩ trình biên dịch thêm phần đệm ở vị trí đầu tiên? Chắc chắn không lãng phí bộ nhớ cho bất cứ điều gì;)
Mecki

4
@Demetri: ví dụ: các giá trị float dương 0 và âm so sánh bằng nhau trên bất kỳ triển khai float nào của IEEE, nhưng chúng không có cùng biểu diễn đối tượng. Vì vậy, thực sự tôi không nên nói rằng nó hoạt động trên "hầu hết tất cả các trình biên dịch", nó sẽ thất bại trong bất kỳ triển khai nào cho phép bạn lưu trữ số không âm. Tôi có lẽ đã nghĩ đến các biểu diễn số nguyên buồn cười tại thời điểm tôi đưa ra nhận xét.
Steve Jessop

4
@Demetri: nhưng nhiều người có chứa phao và người hỏi hỏi "làm thế nào để bạn so sánh các cấu trúc", chứ không phải "làm thế nào để bạn so sánh các cấu trúc không chứa phao". Câu trả lời này cho biết bạn có thể thực hiện một so sánh nông với memcmpđiều kiện bộ nhớ đã bị xóa trước. Mà gần làm việc nhưng không đúng. Tất nhiên, câu hỏi cũng không định nghĩa "đẳng thức", vì vậy nếu bạn đặt nó có nghĩa là "đẳng thức byte khôn ngoan của biểu diễn đối tượng" thì memcmpthực hiện chính xác điều đó (cho dù bộ nhớ có bị xóa hay không).
Steve Jessop

22

Nếu bạn làm điều đó nhiều, tôi sẽ đề nghị viết một hàm so sánh hai cấu trúc. Bằng cách đó, nếu bạn thay đổi cấu trúc, bạn chỉ cần thay đổi so sánh ở một nơi.

Về cách thực hiện .... Bạn cần so sánh từng yếu tố riêng lẻ


1
Tôi sẽ viết một hàm riêng biệt ngay cả khi tôi chỉ sử dụng nó một lần.
Sam

18

Bạn không thể sử dụng memcmp để so sánh các cấu trúc cho sự bình đẳng do các ký tự đệm ngẫu nhiên tiềm năng giữa các trường trong các cấu trúc.

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

Ở trên sẽ thất bại cho một cấu trúc như thế này:

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

Bạn phải sử dụng so sánh thành viên khôn ngoan để được an toàn.


25
Không có khả năng được đệm sau khi gấp đôi; char sẽ được sắp xếp hoàn hảo đầy đủ ngay lập tức sau khi tăng gấp đôi.
Jonathan Leffler

7

@Greg đúng là người ta phải viết các hàm so sánh rõ ràng trong trường hợp chung.

Có thể sử dụng memcmp nếu:

  • các cấu trúc không chứa các trường dấu phẩy động có thể NaN.
  • các cấu trúc không chứa phần đệm (sử dụng -Wpadded với tiếng kêu để kiểm tra điều này) HOẶC các cấu trúc được khởi tạo rõ ràng với memsetlúc khởi tạo.
  • không có loại thành viên nào (như Windows BOOL ) có các giá trị riêng biệt nhưng tương đương.

Trừ khi bạn đang lập trình cho các hệ thống nhúng (hoặc viết thư viện có thể được sử dụng trên chúng), tôi sẽ không lo lắng về một số trường hợp góc trong tiêu chuẩn C. Sự phân biệt con trỏ gần và xa không tồn tại trên bất kỳ thiết bị 32 hoặc 64 bit nào. Không có hệ thống không nhúng mà tôi biết có nhiềuNULL con trỏ.

Một tùy chọn khác là tự động tạo các hàm bằng. Nếu bạn đặt các định nghĩa cấu trúc của bạn ra một cách đơn giản, có thể sử dụng xử lý văn bản đơn giản để xử lý các định nghĩa cấu trúc đơn giản. Bạn có thể sử dụng libclang cho trường hợp chung - vì nó sử dụng cùng một lối vào như Clang, nên nó xử lý tất cả các trường hợp góc chính xác (chặn lỗi).

Tôi chưa thấy một thư viện tạo mã như vậy. Tuy nhiên, nó xuất hiện tương đối đơn giản.

Tuy nhiên, đó cũng là trường hợp các hàm bình đẳng được tạo ra như vậy thường làm sai ở cấp ứng dụng. Ví dụ, hai UNICODE_STRINGcấu trúc trong Windows nên được so sánh nông hay sâu?


2
Hoàn toàn khởi tạo các cấu trúc với memset, v.v. không đảm bảo giá trị của các bit đệm sau khi ghi thêm vào một thành phần cấu trúc, xem: stackoverflow.com/q/52684192/689161
gengkev

4

Lưu ý rằng bạn có thể sử dụng memcmp () trên các vị trí không tĩnh mà không phải lo lắng về phần đệm, miễn là bạn không khởi tạo tất cả các thành viên (cùng một lúc). Điều này được xác định bởi C90:

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


1
Có thực sự được chỉ định rằng {0, }cũng sẽ không có bất kỳ byte đệm nào?
Alnitak

GCC ít nhất là 0 byte đệm cho các cấu trúc được khởi tạo một phần như được trình bày tại liên kết ở trên và stackoverflow.com/questions/13056364/ Chi tiết mà C11 chỉ định hành vi đó.
pixelbeat

1
Nói chung không hữu ích lắm, bởi vì tất cả các phần đệm trở nên không xác định khi gán cho bất kỳ thành viên nào
MM

2

Nó phụ thuộc vào việc câu hỏi bạn đang hỏi là:

  1. Có phải hai cấu trúc này là cùng một đối tượng?
  2. Họ có cùng giá trị không?

Để tìm hiểu xem chúng có phải là cùng một đối tượng hay không, so sánh các con trỏ với hai cấu trúc cho sự bằng nhau. Nếu bạn muốn tìm hiểu chung nếu chúng có cùng giá trị, bạn phải làm một so sánh sâu. Điều này liên quan đến việc so sánh tất cả các thành viên. Nếu các thành viên là con trỏ đến các cấu trúc khác, bạn cũng cần phải truy cập vào các cấu trúc đó.

Trong trường hợp đặc biệt khi các cấu trúc không chứa con trỏ, bạn có thể thực hiện một memcmp để thực hiện so sánh từng bit dữ liệu chứa trong mỗi dữ liệu mà không cần phải biết dữ liệu có nghĩa là gì.

Hãy chắc chắn rằng bạn biết "bằng" nghĩa là gì đối với mỗi thành viên - điều này rõ ràng đối với ints nhưng tinh tế hơn khi nói đến các giá trị dấu phẩy động hoặc các loại do người dùng xác định.


2

memcmpkhông so sánh cấu trúc, memcmpso sánh nhị phân và luôn có rác trong cấu trúc, do đó nó luôn xuất hiện sai khi so sánh.

So sánh yếu tố theo yếu tố an toàn và không thất bại.


1
nếu biến 2 cấu trúc được khởi tạo với calloc hoặc chúng được đặt bằng 0 theo memset để bạn có thể so sánh 2 cấu trúc của mình với memcmp và không phải lo lắng về rác cấu trúc và điều này sẽ cho phép bạn kiếm được thời gian
MOHAMED

1

Nếu các cấu trúc chỉ chứa nguyên thủy hoặc nếu bạn quan tâm đến sự bình đẳng nghiêm ngặt thì bạn có thể làm một cái gì đó như thế này:

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

Tuy nhiên, nếu các cấu trúc của bạn chứa các con trỏ tới các cấu trúc hoặc công đoàn khác thì bạn sẽ cần phải viết một hàm so sánh đúng các nguyên hàm và thực hiện các cuộc gọi so sánh với các cấu trúc khác khi thích hợp.

Tuy nhiên, hãy lưu ý rằng bạn nên sử dụng bộ nhớ (& a, sizeof (struct my_struct), 1) để loại bỏ phạm vi bộ nhớ của các cấu trúc như là một phần của khởi tạo ADT của bạn.


-1

nếu biến 2 cấu trúc được khởi tạo với calloc hoặc chúng được đặt bằng 0 theo memset để bạn có thể so sánh 2 cấu trúc của mình với memcmp và không phải lo lắng về rác cấu trúc và điều này sẽ cho phép bạn kiếm được thời gian


-2

Ví dụ tuân thủ này sử dụng phần mở rộng trình biên dịch gói #pragma từ Microsoft Visual Studio để đảm bảo các thành viên cấu trúc được đóng gói chặt chẽ nhất có thể:

#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))) {
    /* ... */
  }
}

1
Điều đó thực sự chính xác. Nhưng trong hầu hết các trường hợp, bạn không muốn cấu trúc của mình được đóng gói! Khá nhiều hướng dẫn và con trỏ yêu cầu dữ liệu đầu vào được căn chỉnh từ. Nếu không, trình biên dịch cần thêm các hướng dẫn bổ sung để sao chép và sắp xếp lại dữ liệu trước khi thực hiện lệnh thực tế. Nếu trình biên dịch không sắp xếp lại dữ liệu, CPU sẽ đưa ra một ngoại lệ.
Ruud Althuizen
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.