C / C ++: Force Bit Field Order dan Alignment

87

Saya membaca bahwa urutan bidang bit dalam sebuah struct adalah khusus platform. Bagaimana jika saya menggunakan opsi pengemasan khusus kompiler yang berbeda, apakah data jaminan ini akan disimpan dalam urutan yang benar seperti yang tertulis? Sebagai contoh:

struct Message
{
  unsigned int version : 3;
  unsigned int type : 1;
  unsigned int id : 5;
  unsigned int data : 6;
} __attribute__ ((__packed__));

Pada prosesor Intel dengan kompiler GCC, bidang diletakkan di memori seperti yang ditunjukkan. Message.versionadalah 3 bit pertama dalam buffer, dan Message.typediikuti. Jika saya menemukan opsi pengemasan struct yang setara untuk berbagai kompiler, apakah ini akan menjadi lintas platform?

dewald.dll
sumber
17
Karena buffer adalah sekumpulan byte, bukan bit, "3 bit pertama dalam buffer" bukanlah konsep yang tepat. Apakah Anda menganggap 3 bit urutan terendah dari byte pertama sebagai 3 bit pertama, atau 3 bit urutan tertinggi?
kafe
2
Saat transit di jaringan, "3 bit pertama dalam buffer" ternyata terdefinisi dengan sangat baik.
Joshua
2
@Joshua IIRC, Ethernet mentransmisikan bit paling tidak signifikan dari setiap byte terlebih dahulu (itulah sebabnya bit siaran ada di tempatnya).
tc.
Ketika Anda mengatakan "portabel" dan "lintas platform", apa yang Anda maksud? Eksekusi akan mengakses pesanan dengan benar terlepas dari OS target - atau - kode akan dikompilasi terlepas dari toolchain?
Garet Claborn

Jawaban:

103

Tidak, ini tidak akan sepenuhnya portabel. Opsi pengepakan untuk struct adalah ekstensi, dan tidak sepenuhnya portabel. Selain itu, C99 §6.7.2.1, paragraf 10 mengatakan: "Urutan alokasi bidang bit dalam sebuah unit (urutan tinggi ke urutan rendah atau urutan rendah ke urutan tinggi) ditentukan oleh implementasi."

Bahkan kompiler tunggal mungkin meletakkan bidang bit secara berbeda tergantung pada endianness platform target, misalnya.

Stephen Canon
sumber
Ya, GCC, misalnya, secara khusus mencatat bahwa bitfield diatur sesuai ABI, bukan implementasinya. Jadi, hanya menggunakan satu kompiler tidak cukup untuk menjamin pemesanan. Arsitekturnya juga harus diperiksa. Sedikit mimpi buruk untuk portabilitas, sungguh.
underscore_d
10
Mengapa standar C tidak menjamin pesanan untuk bidang bit?
Aaron Campbell
8
Sulit untuk secara konsisten dan mudah mendefinisikan "urutan" bit dalam byte, apalagi urutan bit yang mungkin melintasi batas byte. Definisi apa pun yang Anda tetapkan akan gagal untuk mencocokkan sejumlah besar praktik yang ada.
Stephen Canon
2
implementaiton-defined memungkinkan pengoptimalan khusus platform. Pada beberapa platform, padding antara bidang bit dapat meningkatkan akses, bayangkan empat bidang tujuh-bit dalam int 32 bit: menyelaraskannya di setiap bit ke-8 adalah peningkatan yang signifikan untuk platform yang memiliki pembacaan byte.
peterchen
tidak packedmemberlakukan pemesanan: stackoverflow.com/questions/1756811/… cara menerapkan urutan bit: stackoverflow.com/questions/6728218/gcc-compiler-bit-order
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
45

Bidang bit sangat bervariasi dari kompiler ke kompiler, maaf.

Dengan GCC, mesin big endian meletakkan bit big end terlebih dahulu dan mesin little endian meletakkan bit little end terlebih dahulu.

K&R mengatakan "Anggota bidang [bit-] yang berdekatan dari struktur dikemas ke dalam unit penyimpanan yang bergantung pada implementasi dalam arah yang bergantung pada penerapan. Ketika bidang yang mengikuti bidang lain tidak akan muat ... itu mungkin dibagi antara unit atau unit mungkin empuk. Bidang tanpa nama dengan lebar 0 memaksa bantalan ini ... "

Oleh karena itu, jika Anda memerlukan tata letak biner independen mesin, Anda harus melakukannya sendiri.

Pernyataan terakhir ini juga berlaku untuk non-bitfields karena padding - namun semua kompiler tampaknya memiliki beberapa cara untuk memaksa pengemasan byte dari suatu struktur, seperti yang saya lihat Anda sudah temukan untuk GCC.

Joshua
sumber
Apakah K&R benar-benar dianggap sebagai referensi yang berguna, mengingat itu adalah pra-standarisasi dan (saya asumsikan?) Mungkin telah digantikan di banyak bidang?
underscore_d
1
K&R saya adalah pasca-ANSI.
Joshua
1
Sekarang yang memalukan: Saya tidak menyadari bahwa mereka telah merilis revisi pasca-ANSI. Salahku!
underscore_d
35

Bitfield harus dihindari - mereka tidak terlalu portabel antar kompiler bahkan untuk platform yang sama. dari standar C99 6.7.2.1/10 - "Penentu struktur dan gabungan" (ada kata-kata yang mirip dalam standar C90):

Sebuah implementasi dapat mengalokasikan unit penyimpanan beralamat yang cukup besar untuk menampung bitfield. Jika cukup ruang tersisa, bit-field yang segera mengikuti bit-field lain dalam suatu struktur harus dikemas ke dalam bit yang berdekatan dari unit yang sama. Jika ruang yang tersisa tidak mencukupi, apakah bit-field yang tidak sesuai dimasukkan ke dalam unit berikutnya atau tumpang tindih dengan unit yang berdekatan, ditentukan oleh implementasi. Urutan alokasi bidang bit dalam sebuah unit (orde tinggi ke orde rendah atau orde rendah ke orde tinggi) ditentukan oleh implementasi. Penyelarasan unit penyimpanan yang dapat dialamatkan tidak ditentukan.

Anda tidak dapat menjamin apakah bidang bit akan 'menjangkau' batas int atau tidak dan Anda tidak dapat menentukan apakah bidang bit dimulai di ujung bawah int atau ujung atas int (ini terlepas dari apakah prosesornya big-endian atau little-endian).

Lebih suka bitmask. Gunakan sebaris (atau bahkan makro) untuk mengatur, menghapus dan menguji bit.

Michael Burr
sumber
2
Urutan bitfield dapat ditentukan pada waktu kompilasi.
Greg A. Woods
9
Juga, bitfields sangat disukai ketika berhadapan dengan bit flag yang tidak memiliki representasi eksternal di luar program (misalnya pada disk atau dalam register atau dalam memori yang diakses oleh program lain, dll).
Greg A. Woods
1
@ GregA.Woods: Jika ini masalahnya, berikan jawaban yang menjelaskan caranya. Saya tidak dapat menemukan apa pun selain komentar Anda saat mencari di Google ...
mozzbozz
1
@ GregA.Woods: Maaf, seharusnya saya menulis komentar mana yang saya rujuk. Maksud saya: Anda mengatakan bahwa "Urutan bitfield dapat ditentukan pada waktu kompilasi.". Saya tidak bisa apa-apa tentang itu dan bagaimana melakukannya.
mozzbozz
2
@mozzbozz Kunjungi planix.com/~woods/projects/wsg2000.c dan cari definisi serta penggunaan _BIT_FIELDS_LTOHdan_BIT_FIELDS_HTOL
Greg A. Woods
11

endianness berbicara tentang urutan byte bukan urutan bit. Saat ini , 99% yakin bahwa pesanan bit sudah diperbaiki. Namun, saat menggunakan bitfield, ketangguhan harus diperhitungkan. Lihat contoh di bawah ini.

#include <stdio.h>

typedef struct tagT{

    int a:4;
    int b:4;
    int c:8;
    int d:16;
}T;


int main()
{
    char data[]={0x12,0x34,0x56,0x78};
    T *t = (T*)data;
    printf("a =0x%x\n" ,t->a);
    printf("b =0x%x\n" ,t->b);
    printf("c =0x%x\n" ,t->c);
    printf("d =0x%x\n" ,t->d);

    return 0;
}

//- big endian :  mips24k-linux-gcc (GCC) 4.2.3 - big endian
a =0x1
b =0x2
c =0x34
d =0x5678
 1   2   3   4   5   6   7   8
\_/ \_/ \_____/ \_____________/
 a   b     c           d

// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856
 7   8   5   6   3   4   1   2
\_____________/ \_____/ \_/ \_/
       d           c     b   a
pierrotlefou.dll
sumber
6
Output dari a dan b menunjukkan bahwa endianness masih berbicara tentang urutan bit DAN urutan byte.
Pemrogram Windows
contoh yang bagus dengan masalah pemesanan bit dan pengurutan byte
Jonathan
1
Apakah Anda benar-benar mengkompilasi dan menjalankan kode tersebut? Nilai untuk "a" dan "b" tampaknya tidak logis bagi saya: pada dasarnya Anda mengatakan bahwa kompilator akan menukar camilan dalam satu byte karena ketekunan. Dalam kasus "d", endiannes tidak boleh mempengaruhi urutan byte dalam array char (dengan asumsi panjang char 1 byte); jika kompilator melakukan itu, kita tidak akan dapat mengulang melalui larik menggunakan pointer. Sebaliknya, jika Anda telah menggunakan larik dua bilangan bulat 16 bit, misalnya: uint16 data [] = {0x1234,0x5678}; maka d pasti akan menjadi 0x7856 dalam sistem little endian.
Krauss
6

Sebagian besar waktu, mungkin, tapi jangan bertaruh pada pertanian itu, karena jika Anda salah, Anda akan rugi besar.

Jika Anda benar-benar perlu memiliki informasi biner yang identik, Anda perlu membuat bitfields dengan bitmask - misalnya Anda menggunakan unsigned short (16 bit) untuk Message, dan kemudian membuat sesuatu seperti versionMask = 0xE000 untuk mewakili tiga bit teratas.

Ada masalah serupa dengan penyelarasan dalam struct. Misalnya, Sparc, PowerPC, dan 680x0 CPU semuanya big-endian, dan default umum untuk kompiler Sparc dan PowerPC adalah menyelaraskan anggota struct pada batas 4-byte. Namun, satu kompiler yang saya gunakan untuk 680x0 hanya selaras pada batas 2-byte - dan tidak ada opsi untuk mengubah perataan!

Jadi untuk beberapa struct, ukuran pada Sparc dan PowerPC adalah identik, tetapi lebih kecil pada 680x0, dan beberapa anggota berada di offset memori yang berbeda di dalam struct.

Ini adalah masalah dengan satu proyek yang saya kerjakan, karena proses server yang berjalan di Sparc akan meminta klien dan mengetahui bahwa itu adalah big-endian, dan menganggap itu hanya dapat menyemprotkan struct biner di jaringan dan klien dapat mengatasinya. Dan itu bekerja dengan baik pada klien PowerPC, dan crash besar-besaran pada klien 680x0. Saya tidak menulis kodenya, dan butuh waktu cukup lama untuk menemukan masalahnya. Tapi mudah untuk diperbaiki begitu saya melakukannya.

Bob Murphy
sumber
1

Terima kasih @BenVoigt atas awal komentar Anda yang sangat berguna

Tidak, mereka diciptakan untuk menghemat memori.

Sumber Linux memang menggunakan bit field untuk mencocokkan dengan struktur eksternal: /usr/include/linux/ip.h memiliki kode ini untuk byte pertama dari datagram IP

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
        __u8    ihl:4,
                version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
        __u8    version:4,
                ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif

Namun mengingat komentar Anda, saya menyerah mencoba membuat ini berfungsi untuk bidang bit multi-byte frag_off .

Duncan Roe
sumber
-9

Tentu saja jawaban terbaik adalah dengan menggunakan kelas yang membaca / menulis bidang bit sebagai aliran. Menggunakan struktur bidang bit C tidak dijamin. Belum lagi dianggap tidak profesional / malas / bodoh untuk menggunakan coding ini di dunia nyata.

99999999
sumber
5
Saya pikir itu salah untuk menyatakan bahwa itu bodoh untuk menggunakan bidang bit karena menyediakan cara yang sangat bersih untuk mewakili register perangkat keras, yang dibuat untuk model, dalam C.
trondd
13
@trondd: Tidak, mereka dibuat untuk menghemat memori. Bitfield tidak dimaksudkan untuk memetakan ke struktur data luar, seperti register perangkat keras yang dipetakan memori, protokol jaringan, atau format file. Jika mereka dimaksudkan untuk memetakan ke struktur data luar, urutan pengepakan akan distandarisasi.
Ben Voigt
2
Menggunakan bit menghemat memori. Menggunakan bidang bit meningkatkan keterbacaan. Menggunakan lebih sedikit memori lebih cepat. Menggunakan bit memungkinkan untuk operasi atom yang lebih kompleks. Dalam aplikasi luar di dunia nyata, ada kebutuhan untuk kinerja dan operasi atom yang kompleks. Jawaban ini tidak akan berhasil untuk kami.
johnnycrash
@BenVoigt mungkin benar, tetapi jika seorang programmer ingin mengonfirmasi bahwa urutan compiler / ABI mereka cocok dengan yang mereka butuhkan, dan mengorbankan portabilitas cepat yang sesuai - maka mereka pasti dapat memenuhi peran itu. Adapun 9 *, massa otoritatif mana dari "pembuat kode dunia nyata" yang menganggap semua penggunaan bitfield sebagai "tidak profesional / malas / bodoh" dan di mana mereka menyatakannya?
underscore_d
2
Menggunakan lebih sedikit memori tidak selalu lebih cepat; seringkali lebih efisien untuk menggunakan lebih banyak memori dan mengurangi operasi pasca-baca, dan mode prosesor / prosesor dapat membuatnya semakin nyata.
Dave Newton