So sánh một chút với một boolean


12

Giả sử tôi có một bộ cờ, được mã hóa trong uint16_t flags. Ví dụ , AMAZING_FLAG = 0x02. Bây giờ, tôi có một chức năng. Hàm này cần kiểm tra xem tôi có muốn thay đổi cờ không, vì nếu tôi muốn làm điều đó, tôi cần viết vào flash. Và đó là đắt tiền. Vì vậy, tôi muốn một kiểm tra cho tôi biết nếu flags & AMAZING_FLAGbằng doSet. Đây là ý tưởng đầu tiên:

setAmazingFlag(bool doSet)
{
    if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
        // Really expensive thing
        // Update flags
    }
}

Đây không phải là một tuyên bố nếu trực quan. Tôi cảm thấy nên có một cách tốt hơn, đại loại như:

if ((flags & AMAZING_FLAG) != doSet){

}

Nhưng điều này không thực sự hoạt động, truedường như bằng 0x01.

Vì vậy, có một cách gọn gàng để so sánh một chút với một boolean?


2
Nó không hoàn toàn rõ ràng logic của bạn cần là gì. Đây có phải là : (flags & AMAZING_FLAG) && doSet?
kaylum

Câu hỏi không rõ ràng. Chúng tôi muốn kiểm tra xem 'cờ' có phải là 0x01 hay không. Có phải đó là bạn muốn? Nếu có, thì chúng ta có thể sử dụng toán tử bitwise '&'.
Vikas Vijayan

nếu bạn muốn làm cho cuộc gọi dễ đọc hơn thì setAmazedFlag chỉ khi do set là đúng, thì tên hàm sẽ kiểm tra tốt hơn nếu không bạn có một chức năng có thể hoặc không thể làm những gì mà tên đó nói, làm cho việc đọc mã xấu
AndersK

Nếu tất cả những gì bạn đang cố gắng làm cho nó dễ đọc hơn, chỉ cần tạo một hàm `flagsNotEqual``.
Jeroen3

Câu trả lời:


18

Để chuyển đổi bất kỳ số khác không thành 1 (đúng), có một mẹo cũ: áp dụng !toán tử (không) hai lần.

if (!!(flags & AMAZING_FLAG) != doSet){

4
Hoặc (bool)(flags & AMAZING_FLAG) != doSet- mà tôi tìm thấy trực tiếp hơn. (Mặc dù điều này cho thấy rằng Microsoft có vấn đề với điều này.) Ngoài ra, ((flags & AMAZING_FLAG) != 0)có lẽ chính xác là những gì nó biên dịch và hoàn toàn rõ ràng.
Hội trường Chris

"Ngoài ra, ((cờ & AMAZING_FLAG)! = 0) có lẽ chính xác là những gì nó biên dịch và hoàn toàn rõ ràng". Nó sẽ không Điều gì xảy ra nếu do set là đúng?
Cheiron

@ChrisHall Liệu (bool) 2 có kết quả bằng 1 hay vẫn là 2, trong C?
dùng253751

3
Tiêu chuẩn C11, 6.3.1.2 Loại Boolean: "Khi bất kỳ giá trị vô hướng nào được chuyển đổi thành _Bool, kết quả là 0 nếu giá trị so sánh bằng 0; nếu không, kết quả là 1.". Và 6.5.4 Toán tử truyền: "Trước một biểu thức bằng tên loại được ngoặc đơn sẽ chuyển đổi giá trị của biểu thức thành loại được đặt tên."
Hội trường Chris

@Cheiron, để rõ ràng hơn: ((bool)(flags & AMAZING_FLAG) != doSet)có tác dụng tương tự (((flags & AMAZING_FLAG) != 0) != doSet)và cả hai có lẽ sẽ biên dịch thành chính xác cùng một điều. Đề xuất (!!(flags & AMAZING_FLAG) != doSet)là tương đương, và tôi tưởng tượng sẽ biên dịch theo cùng một điều. Đây hoàn toàn là vấn đề về hương vị mà bạn nghĩ rõ ràng hơn: (bool)dàn diễn viên yêu cầu bạn phải nhớ cách diễn xuất và chuyển đổi sang _Bool hoạt động; các !!đòi hỏi một số bài tập tinh thần khác, hoặc để bạn có thể biết được "lừa".
Hội trường Chris

2

Bạn cần chuyển đổi mặt nạ bit thành câu lệnh boolean, trong C tương đương với các giá trị 0hoặc 1.

  • (flags & AMAZING_FLAG) != 0. Cách phổ biến nhất.

  • !!(flags & AMAZING_FLAG). Hơi phổ biến, cũng có thể sử dụng, nhưng hơi khó hiểu.

  • (bool)(flags & AMAZING_FLAG). Cách C hiện đại từ C99 và hơn thế nữa.

Lấy bất kỳ lựa chọn thay thế nào ở trên, sau đó so sánh nó với boolean của bạn bằng cách sử dụng !=hoặc ==.


1

Từ quan điểm logic, flags & AMAZING_FLAGchỉ là một hoạt động bit che giấu tất cả các cờ khác. Kết quả là một giá trị số.

Để nhận giá trị boolean, bạn sẽ sử dụng so sánh

(flags & AMAZING_FLAG) == AMAZING_FLAG

và bây giờ có thể so sánh giá trị logic này với doSet.

if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)

Trong C có thể có chữ viết tắt, do các quy tắc chuyển đổi ngầm định của số thành giá trị boolean. Vì vậy, bạn cũng có thể viết

if (!(flags & AMAZING_FLAG) == doSet)

để viết mà ngắn gọn hơn. Nhưng phiên bản cũ tốt hơn về khả năng đọc.


1

Bạn có thể tạo mặt nạ dựa trên doSetgiá trị:

#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Bây giờ kiểm tra của bạn có thể trông như thế này:

setAmazingFlag(bool doSet)
{
    const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

    if (flags & set_mask) {
        // Really expensive thing
        // Update flags
    }
}

Trên một số kiến ​​trúc, !!có thể được biên dịch thành một nhánh và bằng cách này, bạn có thể có hai nhánh:

  1. Bình thường hóa bởi !!(expr)
  2. So với doSet

Ưu điểm của đề xuất của tôi là một chi nhánh duy nhất được đảm bảo.

Lưu ý: đảm bảo bạn không giới thiệu hành vi không xác định bằng cách dịch chuyển sang trái hơn 30 (giả sử số nguyên là 32 bit). Điều này có thể dễ dàng đạt được bởi mộtstatic_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");


1
Tất cả các cách biểu thức boolean có khả năng dẫn đến một nhánh. Tôi sẽ tháo rời trước, trước khi áp dụng các thủ thuật tối ưu hóa thủ công lạ.
Lundin

1
@Lundin, chắc chắn, đó là lý do tại sao tôi viết "Trên một số kiến ​​trúc" ...
Alex Lop.

1
Đây chủ yếu là vấn đề đối với trình tối ưu hóa của trình biên dịch và bản thân ISA không nhiều.
Lundin

1
@Lundin tôi không đồng ý. Một số kiến ​​trúc có ISA để thiết lập / thiết lập bit có điều kiện trong trường hợp không cần thiết, không có kiến ​​trúc nào khác. godbolt.org/z/4FDzvw
Alex Lop.

Lưu ý: Nếu bạn làm điều này, vui lòng xác định AMAZING_FLAGtheo AMAZING_FLAG_IDX(ví dụ #define AMAZING_FLAG ((uint16_t)(1 << AMAZING_FLAG_IDX))), vì vậy bạn không có cùng một dữ liệu được xác định ở hai nơi để có thể cập nhật (ví dụ: từ 0x4đến 0x8) trong khi ( IDXcác 2) khác không thay đổi .
ShadowRanger

-1

Có nhiều cách để thực hiện kiểm tra này:

Toán tử ternary có thể tạo ra các bước nhảy tốn kém:

if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))

Bạn cũng có thể sử dụng chuyển đổi boolean, có thể có hoặc không hiệu quả:

if (!!(flags & AMAZING_FLAG) != doSet)

Hoặc thay thế tương đương của nó:

if (((flags & AMAZING_FLAG) != 0) != doSet)

Nếu phép nhân rẻ, bạn có thể tránh nhảy với:

if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)

Nếu flagskhông được ký và trình biên dịch rất thông minh, phép chia bên dưới có thể biên dịch thành một ca đơn giản:

if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)

Nếu kiến ​​trúc sử dụng số học bổ sung của hai, đây là một cách khác:

if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))

Thay phiên, người ta có thể định nghĩa flagslà một cấu trúc với bitfield và sử dụng cú pháp dễ đọc hơn nhiều:

if (flags.amazing_flag != doSet)

Than ôi, cách tiếp cận này thường được tán thành vì đặc điểm kỹ thuật của bitfield không cho phép kiểm soát chính xác đối với việc thực hiện ở cấp độ bit.


Mục tiêu chính của bất kỳ đoạn mã nào là khả năng đọc, mà hầu hết các ví dụ của bạn thì không. Một vấn đề thậm chí còn lớn hơn khi bạn thực sự tải các ví dụ của mình vào một trình biên dịch: hai triển khai ví dụ ban đầu có hiệu suất tốt nhất: số lần nhảy và tải ít nhất (ARM 8.2, -Os) (chúng tương đương). Tất cả các thực hiện khác thực hiện tồi tệ hơn. Nói chung, người ta nên tránh làm những thứ ưa thích (tối ưu hóa vi mô), bởi vì bạn sẽ làm cho trình biên dịch khó khăn hơn để tìm ra những gì đang xảy ra, làm giảm hiệu suất. Do đó, -1.
Cheiron
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.