Câu hỏi “Strlen (s1) - strlen (s2)” không bao giờ nhỏ hơn 0


Tôi hiện đang viết một chương trình C đòi hỏi sự so sánh thường xuyên về độ dài chuỗi nên tôi đã viết hàm trợ giúp sau đây:

int strlonger(char *s1, char *s2) {
    return strlen(s1) - strlen(s2) > 0;
}

Tôi đã nhận thấy rằng hàm trả về true ngay cả khi s1 có chiều dài ngắn hơn s2. Ai đó có thể giải thích hành vi kỳ lạ này?


76
2018-05-06 22:19


gốc


Đó là cách viết của Fortran-66-ish return strlen(s1) > strlen(s2);. - Jonathan Leffler
@TimThomas: Tại sao bạn cung cấp tiền thưởng cho câu hỏi này? Bạn nói rằng nó đã không nhận được đủ sự chú ý, nhưng nó xuất hiện bạn đang khá hài lòng với Câu trả lời của Alex Lockwood. Không chắc chắn những gì nó cần để giành chiến thắng tiền thưởng! :) - eggyal
Đó là một tai nạn, tôi không biết tiền thưởng là gì. -_- Loại xấu hổ ... - Adrian Monk
Tôi đoán nó tốt cho Alex Lockwood bởi vì câu trả lời tuyệt vời của anh ấy sẽ thu hút nhiều sự chú ý hơn ... nên mọi người up-vote Alex Lockwood của câu trả lời !! : D - Adrian Monk
Tôi nghĩ rằng nó tốt hơn cho @TimThomas để giữ tiền thưởng mở cho đến ngày cho phép cuối cùng, để câu hỏi của anh ấy cũng nhận được một số sự chú ý..Hoặc vô tình mất 100 điểm danh tiếng của anh ấy, hãy để anh ấy lấy lại một chút .. - Krishnabhadra


Các câu trả lời:


Những gì bạn đã gặp phải là một số hành vi đặc biệt phát sinh trong C khi xử lý các biểu thức chứa cả số lượng đã ký và chưa ký.

Khi một thao tác được thực hiện khi một toán hạng được ký và giá trị kia không được ký, C sẽ ngầm chuyển đổi đối số đã ký thành unsigned và thực hiện các hoạt động giả định các số không âm. Quy ước này thường dẫn đến hành vi không trực quan cho các nhà khai thác quan hệ như < và >.

Về chức năng trợ giúp của bạn, lưu ý rằng kể từ strlen loại trả về size_t (một số lượng chưa ký), sự khác biệt và so sánh đều được tính bằng cách sử dụng số học chưa ký. Khi nào s1 ngắn hơn s2, Sự khác biệt strlen(s1) - strlen(s2) phải là số âm, nhưng thay vào đó trở thành một số lớn, chưa ký, lớn hơn 0. Như vậy,

return strlen(s1) - strlen(s2) > 0;

trả về 1 thậm chí nếu s1 ngắn hơn s2. Để sửa chức năng của bạn, hãy sử dụng mã này để thay thế:

return strlen(s1) > strlen(s2);

Chào mừng bạn đến với thế giới tuyệt vời của C! :)


Ví dụ bổ sung

Vì câu hỏi này gần đây đã nhận được rất nhiều sự chú ý, tôi muốn cung cấp một vài ví dụ (đơn giản), chỉ để đảm bảo rằng tôi đang có ý tưởng trên. Tôi sẽ giả định rằng chúng tôi đang làm việc với một máy 32 bit sử dụng biểu diễn bổ sung của hai.

Khái niệm quan trọng cần hiểu khi làm việc với các biến unsigned / signed trong C là nếu có sự kết hợp của các đại lượng chưa ký và được ký trong một biểu thức duy nhất, các giá trị đã ký sẽ được ẩn hoàn toàn thành unsigned.

Ví dụ 1:

Xem xét biểu thức sau:

-1 < 0U

Vì toán hạng thứ hai chưa được ký, đầu tiên là ngầm đúc để unsigned, và do đó biểu thức tương đương với so sánh,

4294967295U < 0U

mà tất nhiên là sai. Đây có lẽ không phải là hành vi bạn mong đợi.

Ví dụ # 2:

Hãy xem xét mã sau đây cố gắng tổng hợp các phần tử của một mảng a, trong đó số lượng các phần tử được đưa ra bởi tham số length:

int sum_array_elements(int a[], unsigned length) {
    int i;
    int result = 0;

    for (i = 0; i <= length-1; i++) 
        result += a[i];

    return result;
}

Chức năng này được thiết kế để chứng minh cách dễ dàng các lỗi có thể phát sinh do việc truyền ngầm từ ký sang unsigned. Có vẻ như khá tự nhiên khi truyền tham số length không dấu; sau khi tất cả, những người đã bao giờ muốn sử dụng một chiều dài tiêu cực? Tiêu chí dừng i <= length-1 cũng có vẻ khá trực quan. Tuy nhiên, khi chạy với đối số length tương đương với 0, sự kết hợp của hai sản lượng này là kết quả không mong đợi.

Kể từ thông số length không được ký, tính toán 0-1 được thực hiện bằng cách sử dụng số học không dấu, tương đương với bổ sung mô-đun. Kết quả là sau đó UMax. Các <= so sánh cũng được thực hiện bằng cách so sánh chưa ký, và vì bất kỳ số nào nhỏ hơn hoặc bằng UMax, so sánh luôn giữ. Do đó, mã sẽ cố gắng truy cập các phần tử không hợp lệ của mảng a.

Mã có thể được sửa bằng cách khai báo length là một inthoặc bằng cách thay đổi kiểm tra for vòng lặp được i < length.

Kết luận: Khi nào bạn nên sử dụng Unsigned?

Tôi không muốn nói bất cứ điều gì quá gây tranh cãi ở đây, nhưng đây là một số quy tắc tôi thường tuân thủ khi tôi viết các chương trình trong C.

  • KHÔNG ĐƯỢC chỉ sử dụng vì một số không âm.


174
2018-05-06 22:21



Một ví dụ điển hình khác về cách viết ít hơn làm cho chương trình hơn chính xác. - Kerrek SB
@TimThomas Nó phải đúc một cách hoặc cách khác, và đúc unsigned để ký sẽ mất thông tin, tức là một nửa không gian giá trị. - user207421
Nghiêm túc, phép trừ được thực hiện giữa hai size_t giá trị, được đảm bảo unsigned, và unsigned số học kết thúc tốt đẹp modulo sức mạnh thích hợp của hai. Nơi duy nhất có thể chuyển đổi được ký / không ký hiệu là result > 0 một phần, ở đâu result là size_t giá trị từ phép trừ của hai kích thước. - Jonathan Leffler
Nó không đúc, nó chuyển đổi. Thuật ngữ đúc chỉ đề cập đến một toán tử cast rõ ràng, bao gồm một tên kiểu dấu ngoặc đơn. Nhà điều hành diễn viên chỉ định rõ ràng một chuyển đổi; chuyển đổi có thể rõ ràng hoặc ngầm định. - Keith Thompson
Tôi tìm các số nguyên âm đủ hiếm trong mã của tôi mà tôi sử dụng cách tiếp cận và sử dụng ngược lại unsigned int trừ khi có một số lý do cụ thể không. Điều này có lợi ích là tất cả các hoạt động được xác định rõ ràng (thậm chí "quấn quanh"), mặc dù phải thừa nhận rằng nó có thể yêu cầu quan tâm khi xử lý một số bất bình đẳng. - Joshua Green


strlen trả về một size_t mà là một typedef cho một unsigned kiểu.

Vì thế,

(unsigned) 4 - (unsigned) 7 == (unsigned) - 3

Tất cả các unsigned các giá trị lớn hơn hoặc bằng 0. Thử chuyển đổi các biến được trả lại bởi strlen đến long int.


25
2018-05-06 22:21



ptrdiff_t là dàn diễn viên di động chính xác. Nó phổ biến cho int dài là một số nguyên 32 bit được ký trên các hệ thống 64 bit (trên các hệ thống 64 bit, đó là các con trỏ có 64 bit). Trong thực tế, cả Visual C ++ và gcc cho x86 và x86_64 đều sử dụng độ dài 32 bit. - Mr Fooz
tôi đã nghĩ ptrdiff_t là để trừ con trỏ, không trừ size_t giá trị ... - Mr Lister
Không có loại POSIX cho "phép trừ size_t các giá trị "; C định nghĩa nó đơn giản size_t vì đây là loại tích phân và loại so khớp. Bạn có thể tranh luận rằng đó là off_t, nhưng đó là thực sự cho offsets tập tin. Vì vậy, tốt nhất bạn sẽ làm là lý do mà kể từ đó size_t được yêu cầu giữ bất kỳ chỉ mục nào nền tảng có thể xử lý, sau đó nó cũng có thể đại diện cho bất kỳ giá trị con trỏ nào, vì nó có thể được sử dụng để lập chỉ mục các byte từ 0. Như vậy ptrdiff_t cần phải có cùng số lượng bit size_t, làm cho nó đơn giản là signed phiên bản của size_t. - Mike DeSimone


Alex Lockwood's câu trả lời là giải pháp tốt nhất (nhỏ gọn, ngữ nghĩa rõ ràng, vv).

Đôi khi nó có ý nghĩa để chuyển đổi một cách rõ ràng sang một hình thức đã ký size_t: ptrdiff_t, ví dụ.

return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;

Nếu bạn làm điều này, bạn sẽ muốn chắc chắn rằng size_t giá trị phù hợp trong một ptrdiff_t (có một số bit ít hơn).


1
2018-06-02 00:26