Mengapa menetapkan nilai ke bidang bit tidak mengembalikan nilai yang sama?

96

Saya melihat kode di bawah ini di posting Quora ini :

#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");
}

Di C & C ++, keluaran kode tidak terduga ,

Dinonaktifkan !!

Meskipun penjelasan terkait "sedikit tanda" diberikan dalam posting itu, saya tidak dapat memahami, bagaimana mungkin kita mengatur sesuatu dan kemudian tidak mencerminkan apa adanya.

Bisakah seseorang memberikan penjelasan yang lebih rinci?


Catatan : Kedua tag tersebut & diperlukan, karena standarnya sedikit berbeda untuk menggambarkan bidang bit. Lihat jawaban untuk spesifikasi C dan spesifikasi C ++ .

iammilind
sumber
46
Karena bitfield dideklarasikan seperti yang intmenurut saya hanya dapat menampung nilai 0dan -1.
Osiris
6
pikirkan saja bagaimana int menyimpan -1. Semua bit diset ke 1. Oleh karena itu, jika Anda hanya memiliki satu bit maka jelas harus -1. Jadi 1 dan -1 dalam 1 bit int adalah sama. Ubah centang ke 'if (s.enabled! = 0)' dan berhasil. Karena 0 itu tidak mungkin.
Jürgen
3
Memang benar bahwa aturan ini sama di C dan C ++. Namun menurut kebijakan penggunaan tag , kita hanya boleh memberi tag ini sebagai C dan menahan diri dari pemberian tag silang jika tidak diperlukan. Saya akan menghapus bagian C ++, itu tidak akan mempengaruhi jawaban yang diposting.
Lundin
8
Sudahkah Anda mencoba mengubahnya menjadi struct mystruct { unsigned int enabled:1; };?
ChatterOne
4
Harap baca kebijakan tag C dan C ++ , terutama bagian tentang pemberian tag silang C dan C ++, yang ditetapkan melalui konsensus komunitas di sini . Saya tidak akan melakukan perang rollback, tetapi pertanyaan ini salah diberi tag C ++. Sekalipun bahasa tersebut kebetulan memiliki sedikit perbedaan karena berbagai TC, buatlah pertanyaan terpisah tentang perbedaan antara C dan C ++.
Lundin

Jawaban:

78

Bit-field sangat tidak ditentukan oleh standar. Dengan adanya kode ini struct mystruct {int enabled:1;};, maka kita tidak tahu:

  • Berapa banyak ruang yang ditempati - jika ada bit / byte padding dan di mana lokasinya di memori.
  • Dimana bit tersebut berada di memori. Tidak ditentukan dan juga tergantung pada endianess.
  • Apakah int:nbitfield akan dianggap sebagai ditandatangani atau tidak.

Mengenai bagian terakhir, C17 6.7.2.1/10 mengatakan:

Sebuah bit-field diartikan sebagai memiliki tipe integer bertanda tangan atau tidak bertanda tangan yang terdiri dari jumlah bit yang ditentukan 125)

Catatan non-normatif yang menjelaskan di atas:

125) Seperti ditentukan dalam 6.7.2 di atas, jika penentu tipe aktual yang digunakan adalah intatau nama-typedef didefinisikan sebagai int, maka itu adalah definisi implementasi apakah bidang-bit ditandatangani atau tidak.

Dalam hal bitfield akan dianggap sebagai signed intdan Anda membuat sedikit ukuran 1, maka tidak ada ruang untuk data, hanya untuk bit tanda. Inilah alasan mengapa program Anda mungkin memberikan hasil yang aneh pada beberapa kompiler.

Praktik yang baik:

  • Jangan pernah menggunakan bit-field untuk tujuan apapun.
  • Hindari menggunakan inttipe bertanda tangan untuk segala bentuk manipulasi bit.
Lundin
sumber
5
Di tempat kerja kami memiliki static_asserts pada ukuran dan alamat bitfields hanya untuk memastikan bahwa bitfield tersebut tidak diisi. Kami menggunakan bitfield untuk register perangkat keras di firmware kami.
Michael
4
@Lundin: Hal buruk dari # define-d mask dan offset adalah kode Anda dikotori dengan shift dan operator AND / OR yang bijak. Dengan bitfield, kompilator mengurusnya untuk Anda.
Michael
4
@Michael Dengan bitfields, kompilator menangani itu untuk Anda. Tidak apa-apa jika standar Anda untuk "mengurus itu" adalah "tidak portabel" dan "tidak dapat diprediksi". Punyaku lebih tinggi dari itu.
Andrew Henle
3
@AndrewHenle Leushenko mengatakan bahwa dari perspektif hanya standar C itu sendiri , terserah pada penerapan apakah ia memilih untuk mengikuti x86-64 ABI atau tidak.
mtraceur
3
@AndrewHenle Benar, saya setuju pada kedua poin. Maksud saya adalah bahwa saya pikir ketidaksepakatan Anda dengan Leushenko bermuara pada fakta bahwa Anda menggunakan "implementasi yang ditentukan" untuk merujuk hanya pada hal-hal yang tidak ditentukan secara ketat oleh standar C atau ditentukan secara ketat oleh platform ABI, dan dia menggunakannya untuk merujuk untuk apa pun yang tidak secara tegas ditentukan hanya oleh standar C.
mtraceur
58

Saya tidak dapat memahami, bagaimana mungkin kita mengatur sesuatu dan kemudian tidak muncul sebagaimana mestinya.

Apakah Anda bertanya mengapa itu mengkompilasi vs. memberi Anda kesalahan?

Ya, itu idealnya memberi Anda kesalahan. Dan memang demikian, jika Anda menggunakan peringatan kompiler Anda. Di GCC, dengan -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;
           ^

Alasan mengapa hal ini dibiarkan menjadi ditentukan oleh implementasi vs. kesalahan mungkin lebih berkaitan dengan penggunaan historis, di mana membutuhkan pemeran berarti melanggar kode lama. Para penulis standar mungkin percaya peringatan sudah cukup untuk mengisi kekosongan bagi mereka yang berkepentingan.

Untuk menambahkan beberapa preskriptiftivisme, saya akan menggemakan pernyataan @ Lundin: "Jangan pernah menggunakan bidang bit untuk tujuan apa pun." Jika Anda memiliki jenis alasan yang baik untuk mendapatkan tingkat rendah dan spesifik tentang detail tata letak memori Anda yang akan membuat Anda berpikir bahwa Anda membutuhkan bitfields, persyaratan terkait lainnya yang hampir pasti Anda miliki akan berjalan berlawanan dengan spesifikasi yang terlalu rendah.

(TL; DR - Jika Anda cukup canggih untuk secara sah "membutuhkan" bidang bit, mereka tidak cukup terdefinisi dengan baik untuk melayani Anda.)

HostileFork mengatakan jangan percaya SE
sumber
15
Penulis standar berada pada hari libur pada hari bab bidang bit dirancang. Jadi petugas kebersihan harus melakukannya. Tidak ada alasan tentang apa pun tentang bagaimana bit-field dirancang.
Lundin
9
Tidak ada alasan teknis yang koheren . Tapi itu membuat saya menyimpulkan bahwa ada alasan politik : untuk menghindari membuat kode atau implementasi yang ada menjadi salah. Tetapi hasilnya adalah sangat sedikit tentang bitfield yang dapat Anda andalkan.
John Bollinger
6
@JohnBollinger Jelas ada politik di tempat, yang menyebabkan banyak kerusakan pada C90. Saya pernah berbicara dengan seorang anggota komite yang menjelaskan sumber banyak omong kosong - standar ISO tidak dapat diizinkan untuk mendukung teknologi tertentu yang ada. Inilah sebabnya mengapa kita terjebak dengan hal-hal tolol seperti dukungan untuk pelengkap 1 dan besaran yang ditandatangani, penandatanganan yang ditentukan implementasi char, dukungan untuk byte yang bukan 8 bit, dll. Mereka tidak diizinkan untuk memberikan komputer tolol kerugian pasar.
Lundin
1
@Lundin Akan menarik untuk melihat kumpulan artikel dan post-mortem dari orang-orang yang percaya pengorbanan telah dibuat karena kesalahan, dan mengapa. Saya bertanya-tanya seberapa banyak studi tentang "kami melakukannya terakhir kali, dan berhasil / tidak berhasil" telah menjadi pengetahuan kelembagaan untuk menginformasikan kasus serupa berikutnya, vs. hanya cerita di kepala orang.
HostileFork mengatakan jangan percaya SE
1
Ini masih terdaftar sebagai poin no. 1 dari prinsip asli C dalam C2x Charter: "Kode yang ada itu penting, implementasi yang ada tidak." ... "tidak ada implementasi yang dianggap sebagai contoh untuk mendefinisikan C: Diasumsikan bahwa semua implementasi yang ada harus sedikit berubah agar sesuai dengan Standar."
Leushenko
23

Ini adalah implementasi perilaku yang ditentukan. Saya membuat asumsi bahwa mesin yang Anda jalankan ini menggunakan bilangan bulat bertanda dua-pujian dan memperlakukan intdalam kasus ini sebagai bilangan bulat bertanda untuk menjelaskan mengapa Anda tidak memasukkan if true bagian dari pernyataan if.

struct mystruct { int enabled:1; };

mendeklarasikan enablesebagai bidang bit 1 bit. Sejak ditandatangani, nilai yang valid adalah -1dan 0. Mengatur bidang untuk 1meluap sedikit kembali ke -1(ini adalah perilaku yang tidak ditentukan)

Pada dasarnya ketika berhadapan dengan bit-field yang ditandatangani, nilai maks adalah 2^(bits - 1) - 1yang 0dalam kasus ini.

NathanOliver
sumber
"setelah ditandatangani, nilai yang valid adalah -1 dan 0". Siapa bilang itu ditandatangani? Ini tidak ditentukan tetapi perilaku yang ditentukan oleh implementasi. Jika ditandatangani, maka nilai yang valid adalah -dan +. Pelengkap 2 tidak masalah.
Lundin
5
@Lundin Nomor pujian pasangan 1 bit hanya memiliki dua kemungkinan nilai. Jika bit disetel, maka karena ini adalah bit tanda, nilainya -1. Jika tidak disetel maka itu "positif" 0. Saya tahu ini adalah implementasi yang ditentukan, saya hanya menjelaskan hasil menggunakan implantasi yang paling umum
NathanOliver
1
Kuncinya di sini adalah bahwa pelengkap 2 atau bentuk bertanda tangan lainnya tidak dapat berfungsi dengan satu bit pun tersedia.
Lundin
1
@JohnBollinger Saya mengerti itu. Itu sebabnya saya memiliki pembeda bahwa ini adalah implementasi yang ditentukan. Setidaknya untuk 3 besar mereka semua memperlakukan intseperti yang ditandatangani dalam kasus ini. Sangat disayangkan bahwa bit-field kurang ditentukan. Pada dasarnya di sini adalah fitur ini, konsultasikan dengan kompiler Anda tentang cara menggunakannya.
NathanOliver
1
@ Lundin, kata-kata standar untuk representasi bilangan bulat bertanda tangan dapat dengan baik menangani kasus di mana ada bit nilai nol, setidaknya dalam dua dari tiga alternatif yang diizinkan. Ini berfungsi karena memberikan nilai tempat (negatif) untuk menandatangani bit, daripada memberi mereka interpretasi algoritmik.
John Bollinger
10

Anda dapat menganggapnya sebagai bahwa dalam sistem pelengkap 2, bit paling kiri adalah bit tanda. Dengan demikian, bilangan bulat bertanda apa pun dengan kumpulan bit paling kiri adalah nilai negatif.

Jika Anda memiliki bilangan bulat bertanda 1-bit, ia hanya memiliki sedikit tanda. Jadi menugaskan 1ke bit tunggal itu hanya dapat mengatur bit tanda. Jadi, ketika membacanya kembali, nilainya diartikan sebagai negatif dan begitu juga -1.

Nilai yang dapat dimiliki oleh integer bertanda tangan 1 bit adalah -2^(n-1)= -2^(1-1)= -2^0= -1dan2^n-1= 2^1-1=0

Paul Ogilvie
sumber
8

Sesuai standar C ++ n4713 , cuplikan kode yang sangat mirip disediakan. Jenis yang digunakan adalah BOOL(khusus), tetapi dapat diterapkan ke jenis apa pun.

12.2.4

4 Jika nilai benar atau salah disimpan ke dalam bidang bitbooldengan ukuran berapa pun (termasuk bidang bit satu), nilai aslibooldan nilai bidang bit akan dibandingkan sama. Jika nilai pencacah disimpan ke dalam bit-field dari jenis enumerasi yang sama dan jumlah bit dalam bit-field cukup besar untuk menampung semua nilai dari jenis enumerasi tersebut (10.2), nilai enumerator asli dan nilai bit-field akan membandingkan sama . [Contoh:

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

- contoh akhir]


Pada pandangan pertama, bagian tebal tampak terbuka untuk interpretasi. Namun, maksud yang benar menjadi jelas saat enum BOOLditurunkan dari 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");
}

Dengan kode di atas memberikan peringatan tanpa -Wall -pedantic:

peringatan: 'mystruct :: enabled' terlalu kecil untuk menampung semua nilai 'enum BOOL' struct mystruct { BOOL enabled:1; };

Outputnya adalah:

Dinonaktifkan !! (saat menggunakan enum BOOL : int)

Jika enum BOOL : intdibuat sederhana enum BOOL, maka outputnya seperti yang ditentukan pasage standar di atas:

Diaktifkan (saat menggunakan enum BOOL)


Oleh karena itu, dapat disimpulkan, seperti beberapa jawaban lain yang dimiliki, inttipe tersebut tidak cukup besar untuk menyimpan nilai "1" hanya dalam satu bit-field.

iammilind
sumber
0

Tidak ada yang salah dengan pemahaman Anda tentang bitfield yang bisa saya lihat. Apa yang saya lihat adalah Anda mendefinisikan ulang mystruct terlebih dahulu sebagai struct mystruct {int enabled: 1; } dan kemudian sebagai struct mystruct s; . Apa yang seharusnya Anda kodekan adalah:

#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");
}
ar18
sumber