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


Saya membaca bahwa urutan bidang bit dalam suatu struct adalah platform spesifik. Bagaimana jika saya menggunakan opsi pengemasan khusus compiler yang berbeda, apakah ini menjamin data 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 saat ditampilkan. Message.version adalah 3 bit pertama di buffer, dan Message.type diikuti. Jika saya menemukan opsi pengemasan struct yang setara untuk berbagai compiler, apakah ini akan menjadi cross-platform?


75
2017-09-29 01:07


asal


Jawaban:


Tidak, itu tidak akan sepenuhnya portabel. Opsi pengemasan untuk struct adalah ekstensi, dan itu sendiri tidak sepenuhnya portabel. Selain itu, C99 §6.7.2.1, paragraf 10 mengatakan: "Urutan alokasi bit-fields dalam suatu unit (tinggi-orde ke orde rendah atau orde rendah ke orde tinggi) adalah implementasi-didefinisikan."

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


90
2017-09-29 01:31



Bidang bit sangat bervariasi dari compiler ke compiler, maaf.

Dengan GCC, mesin endian besar memaparkan bit-bit big end pertama dan mesin endian kecil memaparkan bit-bit kecil terlebih dahulu.

K & R mengatakan "Bersebelahan [bit-] anggota lapangan struktur yang dikemas ke dalam unit penyimpanan yang tergantung pada implementasi dalam arah yang tergantung pada implementasi. Ketika sebuah bidang yang mengikuti bidang lain tidak akan cocok ... mungkin dibagi antara unit atau unit mungkin padded. Sebuah bidang tanpa nama lebar 0 memaksa padding ini ... "

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

Pernyataan terakhir ini juga berlaku untuk non-bitfield karena padding - namun semua penyusun tampaknya memiliki beberapa cara untuk memaksa pengepakan byte struktur, seperti yang saya lihat Anda telah temukan untuk GCC.


41
2017-09-29 01:28



Bitfields harus dihindari - mereka tidak terlalu portabel antara compiler bahkan untuk platform yang sama. dari standar C99 6.7.2.1/10 - "Penentu struktur dan penyatuan" (ada kata yang mirip dalam standar C90):

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

Anda tidak dapat menjamin apakah suatu bidang bit akan 'span' suatu batas int atau tidak dan Anda tidak dapat menentukan apakah bitfield dimulai pada low-end dari int atau high end int (ini tergantung apakah prosesor big-endian atau little-endian).

Lebih suka bitmasks. Gunakan inlines (atau bahkan macro) untuk mengatur, menghapus dan menguji bit.


33
2017-09-29 05:11



endianness berbicara tentang perintah byte bukan perintah bit. Sekarang , 99% yakin bahwa pesanan bit telah diperbaiki. Namun, ketika menggunakan bitfield, endianness harus diambil dalam hitungan. 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
// - little endian : gcc (Ubuntu 4.3.2-1ubuntu11) 4.3.2
a =0x2
b =0x1
c =0x34
d =0x7856

9
2017-09-29 01:31



Sebagian besar waktu, mungkin, tapi jangan bertaruh peternakan di atasnya, karena jika Anda salah, Anda akan kehilangan besar.

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

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

Jadi untuk beberapa struct, ukuran pada Sparc dan PowerPC adalah identik, tetapi lebih kecil pada 680x0, dan beberapa anggota berada dalam offset memori yang berbeda 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 menganggapnya bisa menyemprotkan biner ke jaringan dan klien dapat mengatasinya. Dan itu berfungsi dengan baik pada klien PowerPC, dan jatuh waktu besar pada klien 680x0. Saya tidak menulis kode, dan butuh waktu cukup lama untuk menemukan masalahnya. Tapi itu mudah diperbaiki begitu aku melakukannya.


6
2017-09-29 04:53



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


-5
2017-09-29 05:17