język c - zajętość pamięci na struktury

0

Witam Szanownych kolegów.
Zwracam się z takim pytaniem, załóżmy że mam:

/* keyboard buttons structure */
typedef struct kind_of_press
{
	uint32_t hit : 1;
	uint32_t bounce : 1;
	uint32_t shortpress : 1;
	uint32_t longpress : 1;
	uint32_t repeatpress : 1;	
};

typedef struct buttons
{
	kind_of_press button_zero;
	kind_of_press button_one;
	kind_of_press button_two;
	kind_of_press button_three;
	kind_of_press button_four;
	kind_of_press button_five;
	kind_of_press button_six;
	kind_of_press button_seven;
	kind_of_press button_eight;
	kind_of_press button_nine;
	kind_of_press button_arrow_left;
	kind_of_press button_arrow_right;
	kind_of_press button_arrow_up;
	kind_of_press button_arrow_down;
	kind_of_press button_arrow_enter;
	kind_of_press button_esc;
} keyboard_t;

volatile keyboard_t keyboard;

Jak widać na każdą podstrukturę "kind of press" jest przewidzianych pięć pól bitowych. Pytanie brzmi czy wobec tego na strukturę keyboard_t (czyli na naszą utworzoną instancję "keyboard") kompilator zarezerwuje pięć razy ilość klawiszy czyli 5 * 16 = 80 bitów czyli 10 bajtów ?
Jeśli nie to czy istnieje możliwość zmuszenia go do tego stosując atrybut "packed".
Szczerze mówiąc tylko słyszałem o nim i nie jestem pewien czy ma tutaj zastosowanie do takiej operacji...
Dodam jeszcze tylko że piszę na 32 bitowego AVR'a.
Korzystając z okazji życzę wszystkim Mokrego Dyngusa, spokojnych i radosnych Świąt.

0

Dość dziwne, że struktura zajmuje 10 bajtów. Jesteś przekonany, że tak jest? Na wszystkich platformach testowych (nie ma tam AVR) dostaję spodziewany wynik 4 bajtów.

0

Nie, nie jestem pewien. To było pytanie.
Ale jak może zajmować 4 bajty skoro przecież minimalną ilością potrzebną do niezależnego operowania tymi pięcioma polami bitowymi dla każdego ze zdefiniowanych klawiszy to właśnie 5 * 16 = 80 bitów ?

1

Phew, źle przeczytałem. kind_of_press powinien zajmować 32 bity/4 bajty ponieważ użyłeś uint32_t jako typu zmiennych. Gdybyś użył uint8_t to struktura miałaby wielkość 1.

keyboard_t to struktura składająca się z szesnastu takich struktur. Afaik nie można tego zmienić nawet za pomocą atrybutu packed.

0

Dzięki wielkie. Szkoda że nie można jakoś zmusić kompilatora aby sobie to jakoś po swojemu "posegregował" tak aby przeznaczył na to tyle ile w zasadzie jest potrzeba "patrząc z ludzkiego punktu widzenia" - czyli 10 bajtów.
Możesz jeszcze tylko w skrócie napisać jakie w takim razie jest zastosowanie atrybutu "packed" ?

1

Pozwala nie brać pod uwagę alignmentu zmiennych i umieszczać je jedna za drugą. Np.

struct foo
{
    uint8_t a;
    uint32_t b;
};

Powyższa struktura będzie miała wielkość 8, ponieważ b musi mieć 32-bitowy alignment adresu (czyli adres musi być podzielny przez 4). Z __attribute(packed)__ będzie to 5 bajtów, kosztem wydajności lub błędów w działaniu na niektórych architekturach (unaligned access jest wolniejszy lub w ogóle niewspierany w niektórych przypadkach).

0

Kurcze, nic z tego nie kapuje. Co to jest ten "allignment adresu" ?
Ciągle myślałem że adres to adres, wiadomo że każdy jeden bajt w ramie może być adresowany poprzez 2 lub 4 bajty (zależnie od wielkości tej pamięci bo jak wiadomo 2 bajty są w stanie zaadresować pamięć do około 64kB bo wynika to z liczby 65536).
Kompletnie nie wiem o czym mówisz, zlituj się nade mną proszę i wyjaśnij bo zatrudniłem się jako embedded programista w jednej firmie i nie mogę, po prostu nie mogę takich rzeczy nie wiedzieć. Proszę o wyrozumiałość.

1

Tutaj jest to całkiem fajnie opisane: https://en.wikipedia.org/wiki/Data_structure_alignment

W skrócie: szybsze (lub w ogóle możliwe) jest wczytywanie danych o danej wielkości spod adresu o określonych właściwościach. Często jest to wielkość typu dla typów wbudowanych i wielkość największego alignmentu elementu dla struktur. Tak więc w takim przypadku uint32_t powinien być wyrównany do 32 bitów (czyli np. adres 1024 (0x400) jest ok, ale 1032 (0x408) już nie, chociaż oba są ok dla uint8_t, który ma tylko 8 bitów).

0

No to teraz już fajnie wszystko zrozumiałem. Dziękuję za wyjaśnienia, swoją drogą to proste jak drut. :) Zrozumiałem zastosowanie atrybutu packed.
Niebawem jeszcze chciałbym zrobić pewne próby z zajętością pamięci dla 32 bitowej architektury to się okaże czy rzeczywiście masz rację i czy ta struktura będzie bez tego packed zawierała rzeczywiście te 8 bajtów a nie jak cały czas myślałem, tylko 5.

4

Powyższa struktura będzie miała wielkość 8, ponieważ b musi mieć 32-bitowy alignment adresu (czyli adres musi być podzielny przez 4).

Nie musi. Może.
Implementation-dependent.

W praktyce tak zwykle będzie.

1
Adamos19 napisał(a):

Dzięki wielkie. Szkoda że nie można jakoś zmusić kompilatora aby sobie to jakoś po swojemu "posegregował" tak aby przeznaczył na to tyle ile w zasadzie jest potrzeba "patrząc z ludzkiego punktu widzenia" - czyli 10 bajtów.

Jeśli koniecznie chciałbyś to upakować jak tylko się da to oczywiście można. Trzymasz te dane jako ciąg bajtów i wyciągasz/modyfikujesz tylko ten bit który jest Ci potrzebny.
Coś w stylu:

#define NUM_OF_BUTTONS 16
#define BUTTON_ONE 0
#define BUTTON_TWO 1
// etc...
#define FIELDS_PER_BUTTON 5
#define BUTTON_PRESS_HIT 0
#define BUTTON_PRESS_BOUNCE 1
#define BUTTON_PRESS_SHORT 2
#define BUTTON_PRESS_LONG 3
#define BUTTON_PRESS_REPEAT 4

/* EDIT */
#define BITS_PER_BYTE 8

typedef struct buttons
{
/* oczywiście powinno być  dzielone przez 8 a nie przez 1 
	uint8_t raw[ ( ( NUM_OF_BUTTONS * FIELDS_PER_BUTTON ) / sizeof( uint8_t ) ) + 1 ];  */
        uint8_t raw[ ( ( NUM_OF_BUTTONS * FIELDS_PER_BUTTON ) / BITS_PER_BYTE ) + 1 ];
} keyboard_t;


void GetByteAndBitIndexForField( int button_index, int field_index, uint8_t* out_byteindex, uint8_t* out_bitindex )
{
	const uint8_t bit_index = ( button_index * FIELDS_PER_BUTTON ) + field_index;
	*out_byteindex = bit_index / BITS_PER_BYTE;
	*out_bitindex = bit_index - ( *out_byteindex * BITS_PER_BYTE );
}

int IsValidButtonIndex( int button_index )
{
	return ( button_index >= 0 ? ( button_index < NUM_OF_BUTTONS ? 1 : 0 ) : 0 );
}

int IsValidFieldIndex( int field_index )
{
	return ( field_index >= 0 ? ( field_index < FIELDS_PER_BUTTON ? 1 : 0 ) : 0 );
}

int SetButtonField( keyboard_t* keyboard, int button_index, int field_index )
{
        int result = 0;
	if ( keyboard && IsValidButtonIndex( button_index ) && IsValidFieldIndex( field_index ) ) {
	        uint8_t modified_byte, modified_bit;
	        GetByteAndBitIndexForField( button_index, field_index, &modified_byte, &modified_bit );
	        keyboard.raw[ modified_byte ] |= ( 1 << modified_bit );
                result = 1;
        }
	return result;
}


int ClearButtonField( keyboard_t* keyboard, uint8_t button_index, uint8_t field_index )
{
	int result = 0;
	if ( keyboard && IsValidButtonIndex( button_index ) && IsValidFieldIndex( field_index ) ) {
		uint8_t modified_byte, modified_bit;
		GetByteAndBitIndexForField( button_index, field_index, &modified_byte, &modified_bit );
		keyboard->raw[ modified_byte ] &= ~( 1 << modified_bit );
		result = 1;
	}
	return result;
}

I używać tak:
SetButtonField( keyboard, BUTTON_TWO, BUTTON_PRESS_LONG );

Mogłem się oczywiście gdzieś walnąć, ale głównie chodzi mi o idee ;)
Można by też to zrobić w bardziej cywilizowany sposób, żeby np. wyciągać interesujące nas bity i zwracać przerzutowane na strukturę.

E: Poprawka oczywistego błędu - BITS_PER_BYTE zamiast sizeof(uint8_t)

0

Rozwiązanie które przedstawił kolega Tajny Agent (mój przedmówca) jest nieoptymalne.
W woli jasności: chodziło mi o to aby maksymalnie upakować struktury zawierające pięć flag zerojedynkowych a kolega tutaj przedstawił pomysł aby na każdą taką flagę przeznaczyć jeden bajt. To jest jeszcze gorsze niż to co ja zrobiłem bo w moim przypadku ilość zarezerwowanej pamięci RAM wynosi 4 bajty razy ilość klawiszy, w przypadku kolegi 5 bajtów razy ilość klawiszy. W moim przypadku dałoby się zrobić to na strukturach zawierających pięć pól uint8_t pole : 1 i wówczas przy założeniu że architektura byłaby 8-bitowa a nie 32 bitowa, na każdą jedną strukturę przypadłby jeden bajt co razem dałoby 1 bajt razy ilość klawiszy czyli dużo optymalniej.

Dodam tylko że piszę na mikrokontrolery gdzie oszczędność ramu i pamięci flash to podstawa.

Mam jednak jeszcze jedno pytanie do kolegów w związku z atrybutem "packed".
Kolega kq napisał że packed pozwala nie brać pod uwagę allignmentu zmiennych i umieszczać je jedna za drugą, ok, rozumiem.
Ale co kolega ma na myśli pisząc "zmiennych", czy dotyczy to tylko typów podstawowych czy może zdefiniowanych przeze mnie też.
Bo jeśli zdefiniowanych przeze mnie to nic nie stoi na przeszkodzie aby w słówko packed wyposażyć definicję tej zewnętrznej struktury "typedef struct buttons" i czy wówczas będzie tak że istotnie spowoduje to zużycie tylko : (ilość pól bitowych razy ilość klawiszy) = 5 razy 18 = 80 bits = 10 bytes ??

Jeśli nie to dlaczego nie ?

1

Zastrzegłem w poście, że mogłem się gdzieś walnąć no i się walnąłem ;) Zamiast dzielić przez 8 dzieliłem przez 1, niedopatrzenie.

Rozwiązanie jest optymalne. Dla 16 przycisków, które okupują 5 bitów każdy tworzona jest tablica o rozmiarze ( ( 16 * 5 ) / 8 ) + 1 czyli 11 bajtów. (wystarczy 10, ale ten jeden dodatkowy tak dla bezpieczeństwa ;) ).

0

Dokładnie to przeanalizowałem i rzeczywiście we wszystkim macie Szanowni Przedmówcy rację.
Koledze tajnemu agentowi bardzo dziękuję za podanie pomysłu na tacy :)
Dodam tylko że tego bajta bezpieczeństwa nie trzeba , wystarczyłaby tutaj tablica o rozmiarze :

uint8_t raw[ ( ( NUM_OF_BUTTONS * FIELDS_PER_BUTTON ) / BITS_PER_BYTE ) ];

co daje równe 10 bajtów od początku do końca zapychane bitami ;)
Rozwiązanie jest optymalne i obyło się bez zastosowania atrybutów "packed" które bądź co bądź mogą być w różnych przypadkach bardzo przydatne ponieważ pozwalają nam na to ażeby "wyrównywać do bajta" nie biorąc pod uwagę alignmentu zmiennych i umieszczać je jedna za drugą wyrównując do bajta.

0

Bardzo pragnąłbym jednak wiedzieć jak by to miało wyglądać, cytuję : "Można by też to zrobić w bardziej cywilizowany sposób, żeby np. wyciągać interesujące nas bity i zwracać przerzutowane na strukturę." ? Nie rozumiem kompletnie tej idei, czy ktoś może to bliżej wyjaśnić o co koledze "Tajnemu agentowi" chodziło ? Proszę również o informację jak zrobić żeby nazwa użytkownika (np. "Tajny agent") była tak pięknie podświetlana jak jest w Waszych postach jeśli się odwołujecie do konkretnego user'a...?

1 użytkowników online, w tym zalogowanych: 0, gości: 1