Mengapa ukuran struct ini 3, bukan 2?

91

Saya telah mendefinisikan struct ini:

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col; 

The sizeof(col)memberi saya output dari 3, tetapi tidak harus itu 2? Jika saya berkomentar hanya satu elemen, sizeofadalah 2. Saya tidak mengerti mengapa: lima elemen 3 bit sama dengan 15 bit, dan itu kurang dari 2 byte.

Apakah ada "ukuran internal" dalam mendefinisikan struktur seperti ini? Saya hanya perlu klarifikasi, karena dari pengertian saya tentang bahasa selama ini, saya mengharapkan ukuran 2 byte, bukan 3.

Raffaello
sumber
4
Ini mungkin pengoptimalan keselarasan. Ini memulai byte baru, jika ukuran bit berikutnya tidak sesuai dengan ruang yang ditempati sebenarnya.
πάντα ῥεῖ
4
Kecuali jika Anda memiliki beberapa batasan eksternal yang memerlukan pengemasan bit dan platform Anda memberikan beberapa jaminan tambahan atas apa yang ditawarkan standar, ada gunanya menggunakan bitfield.
David Rodríguez - dribeas
3
Perhatikan bahwa untuk C, menggunakan char kurang portabel daripada menggunakan int, stackoverflow.com/a/23987436/23118 .
hlovdal
2
Perhatikan bahwa hampir semua hal tentang bidang bit ditentukan oleh implementasi. Anda mungkin mendapatkan jawaban yang berbeda dari penyusun yang berbeda, dan tidak ada jalan lain. Perhatikan juga bahwa karena Anda tidak menentukan signed charatau unsigned char, Anda tidak dapat mengetahui tanpa melihat dokumentasi apakah compiler akan memperlakukan 'biasa' chardi bidang bit sebagai bertanda tangan atau tidak, dan keputusannya bisa (secara teori) berbeda dari keputusan tentang apakah 'biasa' charditandatangani atau tidak bila digunakan di luar bidang bit.
Jonathan Leffler
3
Secara khusus, di C99, §6.7.2.1 Struct dan specifier serikat, ¶4 Sedikit-bidang harus memiliki tipe yang merupakan versi memenuhi syarat atau wajar tanpa pengecualian dari _Bool, signed int, unsigned int, atau beberapa jenis implementasi yang ditentukan lainnya. Menggunakan charkarena itu jatuh ke dalam 'Jenis implementasi yang ditentukan' kategori.
Jonathan Leffler

Jawaban:

95

Karena Anda menggunakan chartipe yang mendasari bidang Anda, kompilator mencoba mengelompokkan bit per byte, dan karena tidak dapat menempatkan lebih dari delapan bit dalam setiap byte, ia hanya dapat menyimpan dua bidang per byte.

Jumlah total bit yang digunakan oleh struct Anda adalah 15, jadi ukuran yang ideal untuk memuat banyak data adalah a short.

#include <stdio.h>

typedef struct
{
  char A:3;
  char B:3;
  char C:3;
  char D:3;
  char E:3;
} col; 


typedef struct {
  short A:3;
  short B:3;
  short C:3;
  short D:3;
  short E:3;
} col2; 


int main(){

  printf("size of col: %lu\n", sizeof(col));
  printf("size of col2: %lu\n", sizeof(col2));

}

Kode di atas (untuk platform 64-bit seperti milik saya) memang akan menghasilkan 2untuk struct kedua. Untuk sesuatu yang lebih besar dari a short, struct akan mengisi tidak lebih dari satu elemen dari tipe yang digunakan, jadi - untuk platform yang sama - struct akan berakhir dengan ukuran empat untuk int, delapan untuk long, dll.

didierc
sumber
1
Definisi struct yang diusulkan masih salah. Definisi struct yang benar akan menggunakan 'unsigned short'.
pengguna3629249
21
@ user3629249 Mengapa unsigned pendek 'benar'? Jika pengguna ingin menyimpan dari -4 sampai 3 maka short sudah benar. Jika pengguna ingin menyimpan dari 0 sampai 7 maka unsigned short sudah benar. Pertanyaan asli menggunakan tipe bertanda tangan tetapi saya tidak tahu apakah itu disengaja atau tidak disengaja.
Bruce Dawson
2
Mengapa ada perbedaan antara chardan short?
GingerPlusPlus
5
@BruceDawson: Penerapan izin standar harus chartidak ditandatangani…
Thomas Eding
@ThomasEding Benar, standar memungkinkan char menjadi unsigned. Tetapi poin utama saya tetap, bahwa tidak ada alasan yang diberikan untuk mengklaim bahwa unsigned short itu benar (walaupun biasanya akan demikian).
Bruce Dawson
78

Karena Anda tidak dapat memiliki bidang paket bit yang membentang melintasi batas perataan minimum (yaitu 1 byte) sehingga mereka mungkin akan dikemas seperti

byte 1
  A : 3
  B : 3
  padding : 2
byte 2
  C : 3
  D : 3
  padding : 2
byte 3
  E : 3
  padding : 5

(perintah bidang / padding di dalam byte yang sama tidak disengaja, ini hanya untuk memberi Anda ide, karena kompilator dapat meletakkannya sesuai keinginannya)

Mendongkrak
sumber
16

Dua bidang bit pertama cocok menjadi satu char. Yang ketiga tidak bisa cocok dengan itu chardan membutuhkan yang baru. 3 + 3 + 3 = 9 yang tidak cocok dengan karakter 8 bit.

Jadi pasangan pertama mengambil a char, pasangan kedua mengambil a char, dan bidang bit terakhir mendapatkan yang ketiga char.

2501
sumber
15

Kebanyakan kompiler memungkinkan Anda untuk mengontrol padding, misalnya menggunakan #pragmas . Berikut adalah contoh dengan GCC 4.8.1:

#include <stdio.h>

typedef struct
{
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col;

#pragma pack(push, 1)
typedef struct {
    char A:3;
    char B:3;
    char C:3;
    char D:3;
    char E:3;
} col2;
#pragma pack(pop)

int main(){
    printf("size of col: %lu\n", sizeof(col));  // 3
    printf("size of col2: %lu\n", sizeof(col2));  // 2
}

Perhatikan bahwa perilaku default kompilator ada karena suatu alasan dan mungkin akan memberi Anda kinerja yang lebih baik.

Kos
sumber
9

Meskipun standar ANSI C menentukan terlalu sedikit tentang bagaimana bitfields dikemas untuk menawarkan keuntungan yang signifikan atas "kompiler diizinkan untuk mengemas bitfields bagaimanapun mereka mau", namun dalam banyak kasus melarang kompiler untuk mengemas sesuatu dengan cara yang paling efisien.

Secara khusus, jika suatu struktur berisi bitfield, kompiler diperlukan untuk menyimpannya sebagai struktur yang berisi satu atau lebih kolom anonim dari beberapa jenis penyimpanan "normal" dan kemudian secara logis membagi setiap kolom tersebut menjadi bagian bitfield penyusunnya. Jadi, diberikan:

unsigned char foo1: 3;
unsigned char foo2: 3;
unsigned char foo3: 3;
unsigned char foo4: 3;
unsigned char foo5: 3;
unsigned char foo6: 3;
unsigned char foo7: 3;

Jika unsigned char8 bit, kompilator akan diminta untuk mengalokasikan empat bidang dari jenis itu, dan menetapkan dua bidang bit ke semua kecuali satu (yang akan berada di charbidangnya sendiri). Jika semua chardeklarasi telah diganti dengan short, maka akan ada dua field bertipe short, salah satunya akan menampung lima bitfield dan yang lainnya akan menampung dua sisanya.

Pada prosesor tanpa batasan penyelarasan, data dapat disusun lebih efisien dengan menggunakan unsigned shortuntuk lima bidang pertama dan unsigned charuntuk dua terakhir, menyimpan tujuh bidang tiga-bit dalam tiga byte. Meskipun dimungkinkan untuk menyimpan delapan bidang tiga-bit dalam tiga byte, kompilator hanya dapat mengizinkan itu jika ada tipe numerik tiga-byte yang dapat digunakan sebagai tipe "bidang luar".

Secara pribadi, saya menganggap bitfields sebagaimana didefinisikan pada dasarnya tidak berguna. Jika kode perlu bekerja dengan data yang dikemas biner, kode harus secara eksplisit menentukan lokasi penyimpanan dari tipe aktual, dan kemudian menggunakan makro atau cara lain untuk mengakses bitnya. Akan sangat membantu jika C mendukung sintaks seperti:

unsigned short f1;
unsigned char f2;
union foo1 = f1:0.3;
union foo2 = f1:3.3;
union foo3 = f1:6.3;
union foo4 = f1:9.3;
union foo5 = f1:12.3;
union foo6 = f2:0.3;
union foo7 = f2:3.3;

Sintaks seperti itu, jika diizinkan, akan memungkinkan kode untuk menggunakan bitfields secara portabel, tanpa memperhatikan ukuran kata atau urutan byte (foo0 akan berada di tiga bit paling signifikan dari f1, tetapi itu dapat disimpan di alamat yang lebih rendah atau lebih tinggi). Tidak adanya fitur seperti itu, bagaimanapun, makro mungkin satu-satunya cara portabel untuk beroperasi dengan hal-hal seperti itu.

supercat
sumber
2
Kompiler yang berbeda akan mengatur bidang bit secara berbeda. Saya menulis beberapa dokumentasi tentang bagaimana Visual C ++ melakukannya yang mungkin relevan. Ini menunjukkan beberapa jebakan yang mengganggu: randomascii.wordpress.com/2010/06/06/…
Bruce Dawson
Nah, Anda mengatakan padanan penyimpanan dalam tipe normal dan menggunakan operator bit field untuk menyelesaikan variabel tunggal yang menarik dan untuk menyederhanakan mekanisme ini menggunakan beberapa makro. Saya pikir kode yang dihasilkan di c / c ++ melakukan hal seperti ini juga. Menggunakan struct hanya untuk pengorganisasian kode yang "lebih baik", bahkan tidak diperlukan sama sekali.
Raffaello