2008-11-21 13 views
26

Właśnie wykonałem test z bitfieldami, a wyniki mnie zaskakują.Pakowanie bitfieldów w C++ za pomocą boolerów

class test1 { 
public: 
    bool test_a:1; 
    bool test_b:1; 
    bool test_c:1; 
    bool test_d:1; 
    bool test_e:1; 
    bool test_f:1; 
    bool test_g:1; 
    bool test_h:1; 
}; 

class test2 { 
public: 
    int test_a:1; 
    int test_b:1; 
    int test_c:1; 
    int test_d:1; 
    int test_e:1; 
    int test_f:1; 
    int test_g:1; 
    int test_h:1; 
}; 

class test3 { 
public: 
    int test_a:1; 
    bool test_b:1; 
    int test_c:1; 
    bool test_d:1; 
    int test_e:1; 
    bool test_f:1; 
    int test_g:1; 
    bool test_h:1; 
}; 

Wyniki były następujące: -

sizeof(test1) = 1 // This is what I'd expect. 8 bits in a byte 
sizeof(test2) = 4 // Reasonable. Maybe padded out to the size of an int. 
sizeof(test3) = 16 // What??? 

Czy to, czego można się spodziewać, czy to błąd kompilatora? (Codegear C++ Builder 2007, btw ...)

+0

Jeśli chcesz mieć większą kontrolę nad układem struktur bitowych w pamięci, rozważ użycie tego pola bitowego, zaimplementowanego jako plik nagłówkowy biblioteki: [link] (https://github.com/wkaras/C- plus-plus-biblioteka-bit-pola/blob/master/Bitfield.pdf) – WaltK

Odpowiedz

24

Twój kompilator zaaranżował wszystkich członków test3 na granicach wielkości całkowitych. Po wykorzystaniu bloku dla danego typu (pole bitowe całkowite lub boolowskie pole bitowe) kompilator nie przydziela kolejnych pól bitowych innego typu, aż do następnej granicy.

Wątpię, że to błąd. Prawdopodobnie ma to coś wspólnego z podstawową architekturą twojego systemu.

edit:

C++ kompilatory przeznaczy-bitowych pól w pamięci w następujący sposób: kilka kolejnych członków bit-field tego samego typu zostaną przydzielone kolejno. Jak tylko zostanie przydzielony nowy typ, zostanie wyrównany z początkiem następnego bloku pamięci logicznej. Następny blok logiczny będzie zależeć od twojego procesora. Niektóre procesory mogą być wyrównane do 8-bitowych granic, podczas gdy inne mogą być tylko wyrównane do 16-bitowych granic.

W twoim teście 3 każdy element jest innego typu niż poprzedni, więc przydział pamięci będzie wynosił 8 * (minimalny logiczny rozmiar bloku w twoim systemie). W twoim przypadku minimalny rozmiar bloku wynosi dwa bajty (16-bitowe), więc rozmiar testu 3 to 8 * 2 = 16.

W systemie, który może przydzielić 8-bitowe bloki, oczekiwałbym rozmiaru być 8.

+1

Ale jeśli tak, to dlaczego 16, zamiast 20 ((4 + 1) * 4) lub 32 ((4 + 4) * 4)? –

+0

Zgaduję, że twój system nie może dopasować się do niczego mniejszego niż 16-bitowe granice. Kiedy alokacja test_a: 1, zajmuje pierwszy bit 16-bitowego pola. Kiedy test_b: 1 jest alokowany, jest innego typu, więc kompilator uruchamia go na następnej 16-bitowej granicy, w sumie 128 bitów. –

1

Nie tego właśnie bym się spodziewał, ponieważ zamówienie jest znaczące. Jeśli zgrupujesz bool i int, otrzymasz zupełnie inny wynik. Jak pamiętam, bitfieldy działają tylko na wspólnym typie.

7

Wow, to zaskakujące. W GCC 4.2.4 wyniki wynoszą odpowiednio 1, 4 i 4, zarówno w trybach C i C++. Oto program testowy, którego użyłem, który działa zarówno w C99, jak i C++.

#ifndef __cplusplus 
#include <stdbool.h> 
#endif 
#include <stdio.h> 

struct test1 { 
    bool test_a:1; 
    bool test_b:1; 
    bool test_c:1; 
    bool test_d:1; 
    bool test_e:1; 
    bool test_f:1; 
    bool test_g:1; 
    bool test_h:1; 
}; 

struct test2 { 
    int test_a:1; 
    int test_b:1; 
    int test_c:1; 
    int test_d:1; 
    int test_e:1; 
    int test_f:1; 
    int test_g:1; 
    int test_h:1; 
}; 

struct test3 { 
    int test_a:1; 
    bool test_b:1; 
    int test_c:1; 
    bool test_d:1; 
    int test_e:1; 
    bool test_f:1; 
    int test_g:1; 
    bool test_h:1; 
}; 

int 
main() 
{ 
    printf("%zu %zu %zu\n", sizeof (struct test1), sizeof (struct test2), 
          sizeof (struct test3)); 
    return 0; 
} 
+0

spróbuj umieścić struct test3 {public: ....; }; ponieważ istnieje przed nim modyfikator dostępu, kompilator nie może już więcej zmieniać kolejności. (w standardzie jest zdanie stwierdzające, że po takiej rzeczy, aż do drugiej, zmiana kolejności nie jest dozwolona) –

+0

hm. Nie, właśnie to przetestowałem. to nie sprawiło, że był większy:/ –

4

Jako ogólną obserwacją, podpisany int od 1 bit nie ma wiele sensu. Pewnie, możesz prawdopodobnie dowiedzieć się, jak przechowywać w nim 0, ale wtedy zaczyna się kłopot.

Jeden bit musi być bitem znaku, nawet w uzupełnieniu dwójki, ale masz tylko jeden bit do grania. Tak więc, jeśli przydzielisz to jako znak-bit, nie pozostały żadne bity dla rzeczywistej wartości. To prawda, jak Steve Jessop wskazuje w komentarzu, że prawdopodobnie mógłbyś reprezentować -1, jeśli używałbyś dopełnienia dwójki, ale nadal uważam, że typ danych "integer", który może reprezentować tylko 0 i -1, jest raczej dziwną rzeczą.

Dla mnie te typy danych sprawiają, że nie ma (lub, biorąc pod uwagę komentarz Steve'a, mały).

Użyj , aby uczynić go bez znaku, możesz zapisać wartości 0 i 1 w sposób niejednoznaczny.

+0

hehe. Jeśli jest to dopełnienie, może przechowywać tylko plus i minus zero ... Użyłem int jako przykładu. Mój "prawdziwy" kod, kiedy uderzyłem w to, wykorzystywał uty. – Roddy

+10

Jeśli jest to wartość 1-bitowa z podpisem w postaci 2-dopełnienia, wówczas bit oczyszczony reprezentuje 0, a bit set reprezentuje -1. Gdzie jest problem? ;-) –

16

Uważaj bitfields jak wiele jego zachowań jest realizacja (kompilator) zdefiniowane:

Od C++ 03, 9,6 bitfields (str. 163):

Alokacja bitfields zasięgu Obiekt klasy jest zdefiniowany przez implementację jako . Wyrównanie pól bitowych jest zdefiniowane przez implementację. Pola bitowe są spakowane w pewną adresowalną jednostkę alokacji . [Uwaga: alokacja bitów na pola jednostek na niektórych maszynach, a nie na innych. Pola bitowe są przypisywane od prawej do lewej na niektórych komputerach, od lewej do prawej na innych. ]

Oznacza to, że nie jest to błąd w kompilatorze, ale raczej brak standardowej definicji zachowania.

1
#include <iostream> 
using namespace std; 

bool ary_bool4[10]; 

struct MyStruct { 
    bool a1 :1; 
    bool a2 :1; 
    bool a3 :1; 
    bool a4 :1; 
    char b1 :2; 
    char b2 :2; 
    char b3 :2; 
    char b4 :6; 
    char c1; 
}; 

int main() { 
    cout << "char size:\t" << sizeof(char) << endl; 
    cout << "short int size:\t" << sizeof(short int) << endl; 
    cout << "default int size:\t" << sizeof(int) << endl; 
    cout << "long int size:\t" << sizeof(long int) << endl; 
    cout << "long long int size:\t" << sizeof(long long int) << endl; 
    cout << "ary_bool4 size:\t" << sizeof(ary_bool4) << endl; 
    cout << "MyStruct size:\t" << sizeof(MyStruct) << endl; 
    // cout << "long long long int size:\t" << sizeof(long long long int) << endl; 
    return 0; 
} 

char size: 1 
short int size: 2 
default int size: 4 
long int size: 4 
long long int size: 8 
ary_bool4 size: 10 
MyStruct size: 3 
0

Od "Samuela P. Harbison, Guy L. Steele] CA Reference":

Problem:

„Kompilatory mogą swobodnie nakładać ograniczenia na maksymalną wielkość bitu i podaj określone granice adresowania, których pole bitowe nie może przekroczyć. "

manipulacje, które mogą być wykonane w ramach normy: „Pole nieco bez nazwy mogą być również zawarte w celu zapewnienia struktury wyściółkę”

"Podaj długość 0 dla nienazwanego pola bitowego ma specjalne znaczenie - wskazuje, że nie należy już pakować kolejnych pól bitowych w obszarze, w którym znajduje się poprzednie pole bitowe ... Obszar tutaj oznacza pewną zdefiniowaną jednostkę pamięci "

Jest to coś, czego można się spodziewać, lub błąd kompilatora?

Tak więc w C89, C89 z poprawką I, C99 - nie jest to błąd. O C++ Nie wiem, ale myślę, że zachowanie jest podobne.