Tại sao việc gán giá trị cho một trường bit không cho lại cùng giá trị?


96

Tôi đã thấy đoạn mã dưới đây trong bài đăng Quora này :

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

Trong cả C & C ++, đầu ra của mã là không mong muốn ,

Bị vô hiệu hóa !!

Mặc dù giải thích liên quan đến "bit ký hiệu" được đưa ra trong bài đăng đó, tôi không thể hiểu, làm thế nào có thể chúng tôi đặt một cái gì đó và sau đó nó không phản ánh đúng như vậy.

Ai đó có thể giải thích cặn kẽ hơn không?


Lưu ý : Cả hai thẻ & là bắt buộc, vì tiêu chuẩn của chúng hơi khác nhau để mô tả các trường bit. Xem câu trả lời cho đặc tả Cđặc tả C ++ .


46
Vì trường bit được khai báo nên inttôi nghĩ nó chỉ có thể chứa các giá trị 0-1.
Osiris

6
chỉ cần nghĩ về nó cách int lưu trữ -1. Tất cả các bit được đặt thành 1. Do đó, nếu bạn chỉ có một bit thì rõ ràng nó phải là -1. Vì vậy, 1 và -1 trong 1 bit int là giống nhau. Thay đổi séc thành 'if (s.enabled! = 0)' và nó hoạt động. Bởi vì 0 nó không thể được.
Jürgen

3
Đúng là các quy tắc này giống nhau trong C và C ++. Nhưng theo chính sách sử dụng thẻ , chúng ta chỉ nên gắn thẻ này là C và không gắn thẻ chéo khi không cần thiết. Tôi sẽ xóa phần C ++, nó sẽ không ảnh hưởng đến bất kỳ câu trả lời đã đăng nào.
Lundin

8
Bạn đã thử thay đổi nó thành struct mystruct { unsigned int enabled:1; };chưa?
ChatterOne

4
Vui lòng đọc chính sách thẻ C và C ++ , đặc biệt là phần liên quan đến việc gắn thẻ chéo cả hai C và C ++, được thiết lập thông qua sự đồng thuận của cộng đồng tại đây . Tôi sẽ không tham gia vào một số cuộc chiến khôi phục, nhưng câu hỏi này được gắn thẻ C ++ không chính xác. Ngay cả khi các ngôn ngữ xảy ra có một số khác biệt nhỏ do các TC khác nhau, thì hãy đặt một câu hỏi riêng về sự khác biệt giữa C và C ++.
Lundin

Câu trả lời:


77

Các trường bit được định nghĩa rất kém theo tiêu chuẩn. Với mã này struct mystruct {int enabled:1;};, chúng tôi không biết:

  • Dung lượng này chiếm bao nhiêu - nếu có các bit / byte đệm và vị trí của chúng trong bộ nhớ.
  • Vị trí của bit trong bộ nhớ. Không được xác định và cũng phụ thuộc vào đặc tính.
  • Cho dù một trường int:nbit được coi là có dấu hay không có dấu.

Về phần cuối cùng, C17 6.7.2.1/10 nói:

Trường bit được hiểu là có kiểu số nguyên có dấu hoặc không dấu bao gồm số bit được chỉ định 125)

Ghi chú phi quy chuẩn giải thích ở trên:

125) Như đã nêu trong 6.7.2 ở trên, nếu định nghĩa kiểu thực được sử dụng là inthoặc tên typedef được định nghĩa là int, thì nó được xác định thực thi cho dù trường bit được ký hay không có dấu.

Trong trường hợp trường signed intbit được coi là và bạn tạo ra một chút kích thước 1, thì không có chỗ cho dữ liệu, chỉ dành cho bit dấu. Đây là lý do tại sao chương trình của bạn có thể cho kết quả kỳ lạ trên một số trình biên dịch.

Thực hành tốt:

  • Không bao giờ sử dụng trường bit cho bất kỳ mục đích nào.
  • Tránh sử dụng intkiểu có dấu cho bất kỳ hình thức thao tác bit nào.

5
Tại nơi làm việc, chúng tôi có static_asserts về kích thước và địa chỉ của các trường bit chỉ để đảm bảo rằng chúng không bị chèn. Chúng tôi sử dụng trường bit cho thanh ghi phần cứng trong phần sụn của chúng tôi.
Michael

4
@Lundin: Điều tồi tệ với mặt nạ và hiệu số # xác định-d là mã của bạn có nhiều sự thay đổi và toán tử AND / OR khôn ngoan. Với bitfields, trình biên dịch sẽ giải quyết việc đó cho bạn.
Michael

4
@Michael Với bitfields, trình biên dịch sẽ giải quyết việc đó cho bạn. Chà, không sao cả nếu các tiêu chuẩn của bạn về "chăm sóc điều đó" là "không thể di chuyển" và "không thể đoán trước". Của tôi cao hơn thế.
Andrew Henle

3
@AndrewHenle Leushenko đang nói rằng từ quan điểm của bản thân tiêu chuẩn C , việc nó có chọn tuân theo ABI x86-64 hay không là tùy thuộc vào việc triển khai.
mtraceur

3
@AndrewHenle Đúng vậy, tôi đồng ý về cả hai điểm. Quan điểm của tôi là tôi nghĩ rằng sự không đồng ý của bạn với Leushenko bắt nguồn từ việc bạn đang sử dụng "triển khai được xác định" chỉ để chỉ những thứ không được xác định chặt chẽ bởi tiêu chuẩn C cũng như được định nghĩa chặt chẽ bởi nền tảng ABI và anh ấy đang sử dụng nó để tham khảo đối với bất kỳ thứ gì không được xác định chặt chẽ bởi chỉ tiêu chuẩn C.
mtraceur

58

Tôi không thể hiểu, làm thế nào có thể chúng tôi thiết lập một cái gì đó và sau đó nó không hiển thị như nó vốn có.

Bạn đang hỏi tại sao nó biên dịch và cho bạn lỗi?

Vâng, lý tưởng nhất là nó sẽ cho bạn một lỗi. Và nó sẽ xảy ra, nếu bạn sử dụng các cảnh báo của trình biên dịch. Trong GCC, với -Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

Lý do giải thích tại sao điều này lại được xác định bởi việc triển khai so với lỗi có thể liên quan nhiều hơn đến cách sử dụng lịch sử, trong đó yêu cầu diễn viên có nghĩa là phá vỡ mã cũ. Các tác giả của tiêu chuẩn này có thể tin rằng các cảnh báo là đủ để gây ra sự chán nản cho những người liên quan.

Để ném vào một số chủ nghĩa quy định, tôi sẽ lặp lại tuyên bố của @ Lundin: "Không bao giờ sử dụng trường bit cho bất kỳ mục đích nào." Nếu bạn có loại lý do chính đáng để có được các chi tiết về bố cục bộ nhớ ở mức thấp và cụ thể khiến bạn nghĩ rằng bạn cần các trường bit ngay từ đầu, thì các yêu cầu liên quan khác mà bạn gần như chắc chắn có sẽ đi ngược lại với sự không xác định của chúng.

(TL; DR - Nếu bạn đủ tinh vi để hợp pháp "cần" các trường bit, chúng không đủ rõ ràng để phục vụ bạn.)


15
Các tác giả của tiêu chuẩn đã đi nghỉ vào ngày chương trường bit được thiết kế. Vì vậy, người gác cổng đã phải làm điều đó. Không có cơ sở lý luận về bất cứ điều gì liên quan đến cách các trường bit được thiết kế.
Lundin

9
Không có cơ sở lý luận kỹ thuật nhất quán . Nhưng điều đó khiến tôi kết luận rằng có một lý do chính trị : tránh làm cho bất kỳ mã hoặc triển khai hiện có nào không chính xác. Nhưng kết quả là có rất ít trường bit mà bạn có thể dựa vào.
John Bollinger

6
@JohnBollinger Chắc chắn có chính trị, điều này đã gây ra rất nhiều thiệt hại cho C90. Tôi đã từng nói chuyện với một thành viên của ủy ban, người đã giải thích nguồn gốc của rất nhiều điều tào lao - tiêu chuẩn ISO không thể được phép ưu tiên cho một số công nghệ hiện có. Đây là lý do tại sao chúng tôi bị mắc kẹt với những thứ mỉa mai như hỗ trợ cho phần bổ sung của 1 và độ lớn có dấu, ký hiệu được xác định bởi triển khai char, hỗ trợ cho các byte không phải 8 bit, v.v. Chúng không được phép gây bất lợi cho máy tính moronic trên thị trường.
Lundin

1
@Lundin Sẽ rất thú vị khi xem một bộ sưu tập các bài viết và hậu sự từ những người tin rằng sự đánh đổi là do nhầm lẫn và tại sao. Tôi tự hỏi có bao nhiêu nghiên cứu về "chúng tôi đã làm điều đó lần trước, và nó đã / không thành công" đã trở thành kiến ​​thức thể chế để thông báo cho trường hợp tiếp theo như vậy, so với chỉ là những câu chuyện trong đầu mọi người.
HostileFork nói không tin tưởng SE

1
Điều này vẫn được liệt kê là điểm không. 1 trong những nguyên tắc ban đầu của C trong Điều lệ C2x: "Mã hiện tại là quan trọng, các triển khai hiện có thì không." ... "không có bất kỳ triển khai nào được coi là gương mẫu để định nghĩa C: Giả định rằng tất cả các triển khai hiện có phải thay đổi phần nào để phù hợp với Tiêu chuẩn."
Leushenko

23

Đây là hành vi được xác định thực thi. Tôi đang đưa ra giả định rằng các máy bạn đang chạy điều này sử dụng số nguyên có dấu twos-khen và inttrong trường hợp này là số nguyên có dấu để giải thích lý do tại sao bạn không nhập if true một phần của câu lệnh if.

struct mystruct { int enabled:1; };

khai báo enabledưới dạng trường bit 1 bit. Vì nó được ký, các giá trị hợp lệ là -10. Đặt trường làm 1tràn bit đó sẽ quay trở lại -1(đây là hành vi không xác định)

Về cơ bản khi giao dịch với một trường bit ký giá trị tối đa là 2^(bits - 1) - 1đó là 0trong trường hợp này.


"nó đã được ký, các giá trị hợp lệ là -1 và 0". Ai nói nó được ký? Nó không được xác định nhưng là hành vi do triển khai xác định. Nếu nó được ký, thì các giá trị hợp lệ là -+. Phần bổ sung của 2 không quan trọng.
Lundin

5
@Lundin Một số lời khen 1 bit twos chỉ có hai giá trị khả dĩ. Nếu bit được đặt, thì vì nó là bit dấu, nó là -1. Nếu nó không được thiết lập sau đó nó là "tích cực" 0. Tôi biết điều này là thực hiện được xác định, tôi chỉ giải thích các kết quả bằng cách sử dụng cấy phổ biến nhất
NathanOliver

1
Chìa khóa ở đây là phần bổ sung của 2 hoặc bất kỳ biểu mẫu có dấu nào khác không thể hoạt động với một bit có sẵn.
Lundin

1
@JohnBollinger Tôi hiểu điều đó. Đó là lý do tại sao tôi có discliamer rằng đây là việc triển khai được xác định. Ít nhất đối với 3 người lớn họ đều coi intnhư đã ký kết trong trường hợp này. Thật đáng tiếc khi các trường bit được chỉ định quá thấp. Về cơ bản đây là tính năng này, hãy tham khảo trình biên dịch của bạn về cách sử dụng nó.
NathanOliver 19/1218

1
@Lundin, từ ngữ của tiêu chuẩn để biểu diễn các số nguyên có dấu có thể xử lý hoàn hảo trường hợp không có bit giá trị nào, ít nhất là ở hai trong ba lựa chọn thay thế được phép. Điều này hoạt động bởi vì nó chỉ định các giá trị vị trí (âm) cho các bit ký hiệu, thay vì cung cấp cho chúng một giải thích thuật toán.
John Bollinger

10

Bạn có thể nghĩ rằng trong hệ thống bổ sung của 2, bit ngoài cùng bên trái là bit dấu. Do đó, bất kỳ số nguyên có dấu nào với tập bit ngoài cùng bên trái đều là giá trị âm.

Nếu bạn có một số nguyên có dấu 1 bit, nó chỉ có bit dấu. Vì vậy việc gán 1cho bit đơn đó chỉ có thể đặt bit dấu. Vì vậy, khi đọc ngược lại, giá trị được hiểu là âm và -1.

Các giá trị mà một số nguyên có dấu 1 bit có thể giữ là -2^(n-1)= -2^(1-1)= -2^0= -12^n-1= 2^1-1=0


8

Theo tiêu chuẩn C ++ n4713 , một đoạn mã tương tự được cung cấp. Loại được sử dụng là BOOL(tùy chỉnh), nhưng nó có thể áp dụng cho bất kỳ loại nào.

12.2.4

4 Nếu giá trị true hoặc false được lưu trữ trong một trường bitboolcó kích thước bất kỳ (bao gồm cả một trường bit một bit),boolgiá trịban đầuvà giá trị của trường bit sẽ so sánh bằng nhau. Nếu giá trị của một kiểu liệt kê được lưu trữ trong một trường bit của cùng một kiểu liệt kê và số lượng bit trong trường bit đủ lớn để chứa tất cả các giá trị của kiểu liệt kê đó (10.2), thì giá trị của kiểu liệt kê ban đầu và giá trị của trường bit sẽ so sánh bằng nhau . [ Thí dụ:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

- cuối ví dụ]


Ở cái nhìn đầu tiên, phần in đậm có vẻ mở để giải thích. Tuy nhiên, ý định chính xác trở nên rõ ràng khi enum BOOLcó nguồn gốc từ int.

enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

Với mã trên, nó đưa ra cảnh báo mà không có -Wall -pedantic:

cảnh báo: 'mystruct :: enable' quá nhỏ để chứa tất cả các giá trị của 'enum BOOL' struct mystruct { BOOL enabled:1; };

Đầu ra là:

Bị vô hiệu hóa !! (khi sử dụng enum BOOL : int)

Nếu enum BOOL : intđược thực hiện đơn giản enum BOOL, thì kết quả đầu ra như hình dán tiêu chuẩn ở trên chỉ định:

Được bật (khi sử dụng enum BOOL)


Do đó, cũng có thể kết luận, cũng như một số câu trả lời khác, kiểu đó intkhông đủ lớn để lưu trữ giá trị "1" chỉ trong một trường bit đơn lẻ.


0

Không có gì sai với sự hiểu biết của bạn về các trường bit mà tôi có thể thấy. Những gì tôi thấy là bạn đã định nghĩa lại mystruct trước là struct mystruct {int enable: 1; } và sau đó là struct mystruct s; . Những gì bạn nên mã hóa là:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}
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.