Kontener w C dla zmiennych różnych typów

0

Sprawa wygląda tak jak na pliku załączniku. Mam mieć obiekt, do którego mogę odwoływać się po adresie w zakresie od 1..n. Gdzie n jest stałą. Pole, na które się odnoszę wskazuje na różne typy struktur co na obrazku odwzorowują różne długości wektorów wskazywanych od "pola" adresowego. Mój problem polega na tym, że nie wiem jak to ładnie zorganizować tak, żeby kod był w miarę przejrzysty dla innych i optymalny.
Obecnie myślałem o zmiennej typu void**, dla której będę przypisywał poszczególne struktury. Dla ustalenia uwagi: tablica[adres]->losowa struktura.
Osobiście chciałem zrobić coś "ala obiektowo" czyli, że mam strukture wskaźników na struktury do których chce się odnosić. Jednak nie wiem jak załatwić sprytnie sprawę adresowania. Ma ktoś pomysł jak to wykonać?

Proszę o pomoc tylko w postaci wywodu ideowego, chociaż kodem nie pogardzę :]
Z góry dzięki za wszystko.

1

Może stwórz tablicę struktur. Struktura będzie miała typ i wskaźnik typu void. Coś takiego

struct struktura
{
      unsigned int typ;
      void *ptr;
};

Dzięki typowi będziesz wiedział na jaki wskaźnik rzutować ptr.

0

No dobra to jest faktycznie niezłe. I na pewno łatwiejsze do zaimplementowania, niż to co wymyśliłem na początku. Dzięki stryku.
Jc jeszcze poczekam tak ze 2 dni może jeszcze jakiś ciekawy pomysł ktoś podrzuci. :]

2

Mała poprawka... Pole wyboru typu w strukturze, uczynił bym polem enum. W typie enum zdefiniował bym nazwy wybieranych typów, wskaźnik wskazywał by nie na void * a na unię wszystkich typów jakie masz mieć w tym ... kontenerze :-)
Moim zdaniem chociaż trochę należy się chronić bo void * to bardzo często oznaka ... rozpaczy :-)

2

Chyba, że chcesz nie dowolny typ, ale wlasne struktury - wtedy warto pomyśleć o interface obiektowym i zamiast void * wtedy lepiej dać struct parentclass *.

0
kaczus napisał(a):

Chyba, że chcesz nie dowolny typ, ale własne struktury - wtedy warto pomyśleć o interface obiektowym i zamiast void * wtedy lepiej dać struct parentclass *.

Zgadzam się. Jednak piszę w C i chcę odwoływać się do wartości: bool, uint8_t, uint16_t, uint32_t.

Mokrowski napisał(a):

Mała poprawka... Pole wyboru typu w strukturze, uczynił bym polem enum. W typie enum zdefiniował bym nazwy wybieranych typów, wskaźnik wskazywał by nie na void * a na unię wszystkich typów jakie masz mieć w tym ... kontenerze :-)
Moim zdaniem chociaż trochę należy się chronić bo void * to bardzo często oznaka ... rozpaczy :-)

Widzę, że muszę się trochę doprecyzować. Ten kontenerek ma za zadanie umożliwić konfigurację mikrokontrolera z poziomu jakiegoś interfejsu (np. SPI, I2C). Osobiście wolał bym zrobić zmienną 32bitową i ustawiać poszczególne bity. Jednak mam już zdefiniowane odgórnie adresy(1,2,...,n) i jakiej wielkości zmienne są pod poszczególnym adresem, stąd moje zapytanie.
Unia z tego co pamiętam ma rozmiar największego elementu. W takim przypadku jeśli będę chciał by taka struktura przechowywała wartość bool w mikrokontrolerze to marnuje dużo pamięci. Użycie do tego celu voida pozwoli mi na zaoszczędzenie pamięci. Co więcej dostęp do tego będzie wygodniejszy: kontener[adres]->wartość.
Moim zdaniem rozpacz jest wtedy, gdy użyje się go w głupim celu, albo z lenistwa. :P
Jak do tej pory wydaje mi się, że koncepcja stryka chyba najlepiej pasuje do czegoś takiego:

typedef struct {
	x_t value; // wartość przechowująca dane o nieznanym typie
	x_t reset_value;
	acces_t acces; // enum
} reg_t;

Ps. Dzięki za zainteresowanie tematem (nie spodziewałem się tego :D).

0

Zaczynając powoli implementacje tego wszystkiego natknąłem się na ciekawą rzecz, której do końca nie rozumiem...
Nie wiem dlaczego obliczając offset adresu w tablicy z otrzymuję wartość różne od tych używając operatora []?
Wydaję mi się, że wszystko jest poprawnie zrobione...

 #include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef enum {
	RW, R
} acces_t;

typedef struct {
	int size;
	acces_t acces;
} reg_t;

int main(void) {

	reg_t* tablica;
	int size = 12;

	tablica = (reg_t*) malloc(size * sizeof(reg_t));
	tablica[0].acces = RW;
	tablica[0].size = 8;
	tablica[1].acces = R;
	tablica[1].size = 11;

	printf("Wartosc acces: %d\n", (tablica + 0 * sizeof(reg_t))->acces);
	printf("Wartosc rozmiar: %d\n", (tablica + 0 * sizeof(reg_t))->size);
	printf("Wartosc acces: %d\n", (tablica + 1 * sizeof(reg_t))->acces);
	printf("Wartosc rozmiar: %d\n", (tablica + 1 * sizeof(reg_t))->size);

	printf("Wartosc acces: %d\n", tablica[0].acces);
	printf("Wartosc rozmiar: %d\n", tablica[0].size);
	printf("Wartosc acces: %d\n", tablica[1].acces);
	printf("Wartosc rozmiar: %d\n", tablica[1].size);
	return EXIT_SUCCESS;
}

Wynik z konsoli:
Wartosc acces: 0
Wartosc rozmiar: 8
Wartosc acces: 0
Wartosc rozmiar: 0

Wartosc acces: 0
Wartosc rozmiar: 8
Wartosc acces: 1
Wartosc rozmiar: 11

2

Powinno być

 printf("Wartosc acces: %d\n", (tablica + 0 )->acces);
    printf("Wartosc rozmiar: %d\n", (tablica + 0)->size);
    printf("Wartosc acces: %d\n", (tablica + 1 )->acces);
    printf("Wartosc rozmiar: %d\n", (tablica + 1)->size); 

Bez tych sizeof. Kompilator sam pooblicza rozmiar danej i odpowiednio uzupełni wskaźniki

0

Jeszcze raz dzięki. Kodowanie po północy chyba mi nie służy. :P

0

Nie wiem jak fachowo zacytować i odpowiedzieć na komentarz do posta na tym forum to robię to w taki sposób:

Wskaźnik zawiera zawsze adres o takim samym rozmiarze. Bez względu na to czy wskazuje na unię, long long czy obiekt. Unię wprowadzasz tylko po to by zawęzić typy do tych określonych czyli implementujesz bez dodatkowych kosztów ograniczoną i niedoskonałą kontrolę typu której by nie było gdyby był to wyłącznie void *. Dobrze że powiedziałeś że to MCU. To zupełnie inny problem (kombinacje void const i ofsetów struktur). Radzę zerknąć do implementacji portów w CMSIS dla ARM. Robi się to zupełnie inaczej niż Ty chcesz :-) - Mokrowski dzisiaj, 10:13

Implementacja portów:

 typedef struct
{
  __IO uint32_t MODER;        /*!< GPIO port mode register,                                  Address offset: 0x00 */
  __IO uint32_t OTYPER;       /*!< GPIO port output type register,                           Address offset: 0x04 */
  __IO uint32_t OSPEEDR;      /*!< GPIO port output speed register,                          Address offset: 0x08 */
  __IO uint32_t PUPDR;        /*!< GPIO port pull-up/pull-down register,                     Address offset: 0x0C */
  __IO uint32_t IDR;          /*!< GPIO port input data register,                            Address offset: 0x10 */
  __IO uint32_t ODR;          /*!< GPIO port output data register,                           Address offset: 0x14 */
  __IO uint32_t BSRR;         /*!< GPIO port bit set/reset registerBSRR,                     Address offset: 0x18 */
  __IO uint32_t LCKR;         /*!< GPIO port configuration lock register,                    Address offset: 0x1C */
  __IO uint32_t AFR[2];       /*!< GPIO alternate function register,                    Address offset: 0x20-0x24 */
  __IO uint32_t BRR;          /*!< GPIO bit reset register,                                  Address offset: 0x28 */
}GPIO_TypeDef; 

Tak się składa, że kod który piszę ma być na procek pod architekturę ARM (STM32F1). A z przyzwyczajenia z AVR'ów nie przepadam za tymi funkcjami od ST i wole pisać własne funkcje, więc z tymi strukturami dość często obcuję :P. Ale do rzeczy.
To co sugerowałeś Mokrowski moim zdaniem jest najlepszą i najbardziej słuszną opcją. Gdybym to robił sam od podstaw to pewnie tak bym to zorganizował. Jednak nie ja ustaliłem sposób komunikacji, wielkość zmiennych i rozmieszczenie adresów. Po prostu podpinam się do istniejącego już urządzenia i mam zapewnić mu możliwość zdalnego sterowania tym co ja stworze przez jakiś protokół. Co ma swoje plusy i minusy. Największym minusem jest to, że ktoś nie przemyślał adresowania, pokażę to na poniższym przykładzie:
Dla ustalenia uwagi - zmienne/struktury, które przechowują odpowiednie wartości, dalej będę je określał mianem rejestrów.

/\ 1b | 1b | 1b | 1b | 8b | 16b | 4b+/+4b.
! !
Tu początek zmiennej Tutaj koniec

Tu gdzie kończy się zmienna ja mam zmieścić wartość 8bitową stąd 4b+4b. Pewnie zaraz ktoś napisze, abym zmarnował te 4bity i zaczął je zapisywać w innej zmiennej. Ok, w porządku :D. Jednak tu pojawia się inny problem. Jak do tych zmiennych się odnosić skoro adres rejestru nie uwzględnia długości danych. W przypadku powyższego przykładu mam coś takiego:
/\ 1 | 2 | 3 | 4 | 5 | 6 | 7 /
Gdzie kolejne wartości to po prostu adresy pod jakie muszę się odnosić. Ułożenie zmiennych jest strasznie randomowe więc nie da się jakoś łatwo obliczać tego adresu. A ma być to na tyle proste i czytelne, żeby inna osoba, która z tego będzie korzystała bez problemu rozszerzyła tego funkcjonalność. Z flashem i ramem, aż takiej biedy na ARM'ach nie ma, więc ostatecznie skuszę się na opcję stryka, tyle że jednak zdefiniuję sobie te unie. Okazało się, że pracowanie w ten sposób na void'ach w C jest strasznie upierdliwe.

Pozdro, dzięki wam naprawdę za takie zaangażowanie :]

1

@DuMaM poczytaj o dwóch rzeczach: data alignment oraz - to jest akurat cecha Cortexów M3 w górę - o bitbandingu. To może Ci wiele pomóc (albo i skomplikować bo daje nowe możliwości ;) ). Co do tego drugiego - popatrz na bibliotekę od TI do ichnich ARMów (Stellaris czy inna Tiva), oni tam mają makra w stylu "setBit" oraz "setBitDirectMemAccess" czy jakoś na tę modłę, ST w ogóle tego nie stosuje, nie wiem czemu w sumie, bo to świetny ficzer...

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