Statyczna alokacja nieprzezroczystych typów danych

Bardzo często malloc () jest absolutnie niedozwolone podczas programowania dla systemów wbudowanych. Przez większość czasu jestem w stanie sobie z tym poradzić, ale jedna rzecz mnie irytuje: powstrzymuje mnie to od używania tak zwanych "nieprzezroczystych typów", aby umożliwić ukrywanie danych. Normalnie zrobiłbym coś takiego:

// In file module.h
typedef struct handle_t handle_t;

handle_t *create_handle();
void operation_on_handle(handle_t *handle, int an_argument);
void another_operation_on_handle(handle_t *handle, char etcetera);
void close_handle(handle_t *handle);


// In file module.c
struct handle_t {
    int foo;
    void *something;
    int another_implementation_detail;
};

handle_t *create_handle() {
    handle_t *handle = malloc(sizeof(struct handle_t));
    // other initialization
    return handle;
}

Proszę bardzo: create_handle () wykonuje malloc (), aby utworzyć 'instancję'. Konstrukcją często używaną, aby zapobiec konieczności malloc() jest zmiana prototypu create_handle () jak to:

void create_handle(handle_t *handle);

I wtedy wywołujący może utworzyć uchwyt w ten sposób:

// In file caller.c
void i_am_the_caller() {
    handle_t a_handle;    // Allocate a handle on the stack instead of malloc()
    create_handle(&a_handle);
    // ... a_handle is ready to go!
}

Ale niestety ten kod jest oczywiście nieprawidłowy, rozmiar handle_t nie jest znany!

Nigdy nie znalazłem rozwiązania, które rozwiąże to we właściwy sposób. Bardzo chciałbym wiedzieć, czy ktoś ma na to odpowiedni sposób, a może zupełnie inne podejście, aby umożliwić ukrywanie danych w C (nie używając statycznych globali w module.c Oczywiście trzeba umieć tworzyć wiele instancji).
Author: Bart, 2010-12-14

9 answers

Możesz użyć funkcji _alloca. Uważam, że nie jest to do końca Standard, ale z tego co wiem, implementują go prawie wszystkie popularne Kompilatory. Gdy używasz go jako argumentu domyślnego, przydziela on ze stosu wywołującego.

// Header
typedef struct {} something;
int get_size();
something* create_something(void* mem);

// Usage
handle* ptr = create_something(_alloca(get_size()); // or define a macro.

// Implementation
int get_size() {
    return sizeof(real_handle_type);
}
something* create_something(void* mem) {
    real_type* ptr = (real_type_ptr*)mem;
    // Fill out real_type
    return (something*)mem;
}

Możesz również użyć jakiegoś rodzaju pół-sterty puli obiektów - jeśli masz maksymalną liczbę obecnie dostępnych obiektów, możesz przeznaczyć dla nich całą pamięć statycznie, a także tylko bit-shift, dla których są one obecnie używane.

#define MAX_OBJECTS 32
real_type objects[MAX_OBJECTS];
unsigned int in_use; // Make sure this is large enough
something* create_something() {
     for(int i = 0; i < MAX_OBJECTS; i++) {
         if (!(in_use & (1 << i))) {
             in_use &= (1 << i);
             return &objects[i];
         }
     }
     return NULL;
}

Mój trochę się przesuwa, dawno tego nie robiłam, ale mam nadzieję, że rozumiesz.

 15
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-14 15:19:54

Jednym ze sposobów byłoby dodanie czegoś w rodzaju

#define MODULE_HANDLE_SIZE (4711)

Do nagłówka publicznego module.h. Ponieważ stwarza to niepokojący wymóg utrzymywania synchronizacji z rzeczywistym rozmiarem, linia jest oczywiście najlepiej generowana automatycznie przez proces budowania.

Inną opcją jest oczywiście ujawnienie struktury, ale udokumentowanie jej jako nieprzezroczystej i zabraniającej dostępu za pomocą innych środków niż zdefiniowane API. Można to wyjaśnić, robiąc coś like:

#include "module_private.h"

typedef struct
{
  handle_private_t private;
} handle_t;

Tutaj, rzeczywista deklaracja uchwytu modułu została przeniesiona do osobnego nagłówka, aby uczynić go mniej widocznym. Typ zadeklarowany w tym nagłówku jest następnie po prostu zawinięty w żądaną nazwę typedef, upewniając się, że jest prywatny.

Funkcje wewnątrz modułu, które przyjmują handle_t * mogą bezpiecznie uzyskać dostęp private jako wartość handle_private_t, ponieważ jest to pierwszy członek struktury publicznej.

 8
Author: unwind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-01 15:29:46

Jedno rozwiązanie, jeśli utworzyć statyczną pulę obiektów struct handle_t i podać je jako niezbędne. Istnieje wiele sposobów, aby to osiągnąć, ale prosty przykład ilustracyjny:

// In file module.c
struct handle_t 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_t handle_pool[MAX_HANDLES] ;

handle_t* create_handle() 
{
    int h ;
    handle_t* handle = 0 ;
    for( h = 0; handle == 0 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = &handle_pool[h] ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t* handle ) 
{
    handle->in_use = 0 ;
}

Istnieją szybsze sposoby znajdowania nieużywanego uchwytu, można na przykład zachować statyczny indeks, który zwiększa się za każdym razem, gdy uchwyt jest alokowany i "owija się", gdy osiągnie MAX_HANDLES; byłoby to szybsze w typowej sytuacji, w której kilka uchwytów jest przydzielanych przed zwolnieniem dowolnego uchwytu jeden. Jednak dla niewielkiej liczby uchwytów to wyszukiwanie brute-force jest prawdopodobnie odpowiednie.

Oczywiście sam uchwyt nie musi być już wskaźnikiem, ale może być prostym indeksem do ukrytej puli. Wzmocniłoby to ukrywanie danych i ochronę puli przed dostępem z zewnątrz.

Więc nagłówek miałby:

typedef int handle_t ;

A kod zmieniłby się następująco:

// In file module.c
struct handle_s 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_s handle_pool[MAX_HANDLES] ;

handle_t create_handle() 
{
    int h ;
    handle_t handle = -1 ;
    for( h = 0; handle != -1 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = h ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t handle ) 
{
    handle_pool[handle].in_use = 0 ;
}

Ponieważ zwrócony uchwyt nie jest już wskaźnikiem do danych wewnętrznych, a dociekliwy lub złośliwy użytkownik nie może uzyskać do niego dostępu przez uchwyt.

Zauważ, że może być konieczne dodanie mechanizmów zabezpieczających wątek, jeśli otrzymujesz uchwyty w wielu wątkach.

 5
Author: Clifford,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-14 17:15:39

Niestety, wydaje mi się, że typowym sposobem radzenia sobie z tym problemem jest po prostu traktowanie obiektu przez programistę jako nieprzezroczystego - pełna implementacja struktury znajduje się w nagłówku i jest dostępna, po prostu obowiązkiem programisty jest nie używać wewnętrznych elementów bezpośrednio, tylko poprzez API zdefiniowane dla obiektu.

Jeśli to nie wystarczy, kilka opcji może być:

  • użyj C++ jako 'lepszego C' i zadeklaruj wewnętrzne struktury jako private.
  • Uruchom jakiś pre-procesor na nagłówkach tak, że wewnętrzne struktury są zadeklarowane, ale z bezużytecznymi nazwami. Oryginalny nagłówek, z dobrymi nazwami, będzie dostępny dla implementacji API, które zarządzają strukturą. Nigdy nie widziałem, aby użyto tej techniki - to tylko pomysł z głowy, który może być możliwy, ale wydaje się o wiele więcej kłopotów niż jest wart.
  • niech twój kod, który używa nieprzezroczystych wskaźników deklaruje statycznie przydzielone obiekty jako extern (tj., globals) mają wtedy specjalny moduł, który ma dostęp do pełnej definicji obiektu faktycznie deklaruje te obiekty. Ponieważ tylko moduł "specjalny" ma dostęp do pełnej definicji, normalne użycie nieprzezroczystego obiektu pozostaje nieprzezroczyste. Jednak teraz musisz polegać na swoich programistach, aby nie nadużywać faktu, że Twoje obiekty są globalne. Zwiększyłeś również zmianę nazewnictwa kolizji, więc trzeba nimi zarządzać (prawdopodobnie nie jest to duży problem, poza tym, że to może się zdarzyć nieumyślnie-AUĆ!).

Myślę, że ogólnie rzecz biorąc, samo poleganie na programistach, aby przestrzegali zasad korzystania z tych obiektów, może być najlepszym rozwiązaniem(choć używanie podzbioru C++ też nie jest złe moim zdaniem). W zależności od programistów, aby przestrzegać zasad nie używać struktury wewnętrznej nie jest idealny, ale jest to wykonalne rozwiązanie, które jest w powszechnym użyciu.

 3
Author: Michael Burr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-14 15:52:51

Napotkałem podobny problem przy implementacji struktury danych, w której nagłówek struktury danych, który jest nieprzezroczysty, przechowuje wszystkie różne dane, które muszą być przeniesione z operacji do operacji.

Ponieważ ponowna inicjalizacja może spowodować wyciek pamięci, chciałem się upewnić, że implementacja struktury danych sama nigdy nie nadpisuje punktu do przydzielonej pamięci sterty.

To co zrobiłem jest następujące:

/** 
 * In order to allow the client to place the data structure header on the
 * stack we need data structure header size. [1/4]
**/
#define CT_HEADER_SIZE  ( (sizeof(void*) * 2)           \
                        + (sizeof(int) * 2)             \
                        + (sizeof(unsigned long) * 1)   \
                        )

/**
 * After the size has been produced, a type which is a size *alias* of the
 * header can be created. [2/4] 
**/        
struct header { char h_sz[CT_HEADER_SIZE]; };
typedef struct header data_structure_header;

/* In all the public interfaces the size alias is used. [3/4] */
bool ds_init_new(data_structure_header *ds /* , ...*/);

W realizacji plik:

struct imp_header {
    void *ptr1, 
         *ptr2;
    int  i, 
         max;
    unsigned long total;
};

/* implementation proper */
static bool imp_init_new(struct imp_header *head /* , ...*/)
{
    return false; 
}

/* public interface */
bool ds_init_new(data_structure_header *ds /* , ...*/) 
{
    int i;

    /* only accept a zero init'ed header */
    for(i = 0; i < CT_HEADER_SIZE; ++i) {
        if(ds->h_sz[i] != 0) {
            return false;
        }
    }

    /* just in case we forgot something */
    assert(sizeof(data_structure_header) == sizeof(struct imp_header));

    /* Explicit conversion is used from the public interface to the
     * implementation proper.  [4/4]
     */
    return imp_init_new( (struct imp_header *)ds /* , ...*/); 
}

Strona klienta:

int foo() 
{
    data_structure_header ds = { 0 };

    ds_init_new(&ds /*, ...*/);
}
 1
Author: koby m.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-23 17:31:56

Jestem trochę zdezorientowany, dlaczego mówisz, że nie możesz używać malloc (). Oczywiście w systemie wbudowanym masz ograniczoną pamięć i zwykle rozwiązaniem jest posiadanie własnego menedżera pamięci, który mallocs dużą pulę pamięci, a następnie alokuje fragmenty tego w razie potrzeby. W swoich czasach widziałem różne implementacje tego pomysłu.

Aby odpowiedzieć na twoje pytanie, dlaczego nie przydzielisz statycznie tablicy o stałym rozmiarze w module.c dodać flagę "w użyciu" , a następnie mieć create_handle () po prostu zwraca wskaźnik do pierwszego wolnego elementu.

Jako rozszerzenie tego pomysłu, "uchwyt" może być indeksem całkowitym, a nie faktycznym wskaźnikiem, który pozwala uniknąć szansy, że użytkownik spróbuje go nadużywać, rzucając go do własnej definicji obiektu.

 0
Author: AlastairG,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-14 15:24:19

Najmniej ponurym rozwiązaniem, jakie widziałem, było dostarczenie nieprzezroczystej struktury do użycia przez wywołującego, która jest wystarczająco duża, Plus może trochę, wraz z wzmianką o typach używanych w rzeczywistej strukturze, aby zapewnić, że nieprzezroczysta struktura będzie wystarczająco wyrównana w porównaniu z rzeczywistą strukturą:

struct Thing {
    union {
        char data[16];
        uint32_t b;
        uint8_t a;
    } opaque;
};
typedef struct Thing Thing;

Wtedy funkcje pobierają wskaźnik do jednego z nich:

void InitThing(Thing *thing);
void DoThingy(Thing *thing,float whatever);

Wewnętrznie, nie eksponowane jako część API, istnieje struktura, która ma Prawdziwe wewnętrzne:

struct RealThing {
    uint32_t private1,private2,private3;
    uint8_t private4;
};
typedef struct RealThing RealThing;

(to jeden ma uint32_t' and uint8_t ' - to jest powód pojawienia się tych dwóch typów w Unii powyżej.)

Plus prawdopodobnie czas kompilacji, aby upewnić się, że rozmiar RealThing nie przekracza rozmiaru Thing:

typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1];

Następnie każda funkcja w bibliotece wykonuje rzut na swój argument, gdy ma go użyć:

void InitThing(Thing *thing) {
    RealThing *t=(RealThing *)thing;

    /* stuff with *t */
}

Z tą funkcją, wywołujący może tworzyć obiekty o odpowiednim rozmiarze na stosie i wywoływać przeciwko nim funkcje, struktura jest nadal nieprzezroczysta i niektórzy sprawdzają, czy wersja nieprzezroczysta jest wystarczająco duża.

Potencjalnym problemem jest to, że pola mogą być wstawiane do rzeczywistej struktury, co oznacza, że wymaga wyrównania, którego nieprzezroczysta struktura nie ma, a to niekoniecznie potknie kontrolę rozmiaru. Wiele takich zmian zmieni rozmiar struktury, więc zostaną złapane, ale nie wszystkie. Nie jestem pewien żadnego rozwiązania.

Alternatywnie, jeśli masz specjalny nagłówek publiczny, którego biblioteka nigdy nie zawiera, to prawdopodobnie możesz (pod warunkiem testowania na wspieranych kompilatorach...) po prostu napisz swoje publiczne prototypy z jednym typem, a wewnętrzne z drugim. Dobrym pomysłem byłoby uporządkowanie nagłówków tak, aby Biblioteka w jakiś sposób zobaczyła zwróconą do publiczności strukturę Thing, aby można było sprawdzić jej rozmiar.

 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-14 23:25:24

Jest to proste, wystarczy umieścić struktury w privateTypes.plik nagłówkowy H. Nie będzie już nieprzezroczysty, ale będzie prywatny dla programisty, ponieważ znajduje się w prywatnym pliku.

Przykład tutaj: ukrywanie członków w strukturze C

 0
Author: Felipe Lavratti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:09:20

To stare pytanie, ale ponieważ gryzie mnie również, chciałem podać tutaj możliwą odpowiedź (której używam).

Oto przykład:

// file.h
typedef struct { size_t space[3]; } publicType;
int doSomething(publicType* object);

// file.c
typedef struct { unsigned var1; int var2; size_t var3; } privateType;

int doSomething(publicType* object)
{
    privateType* obPtr  = (privateType*) object;
    (...)
}

Zalety : publicType mogą być przydzielane na stosie.

Należy pamiętać, że właściwy typ bazowy musi być wybrany w celu zapewnienia właściwego wyrównania(tzn. nie należy używać char). Zauważ również, że sizeof(publicType) >= sizeof(privateType). Proponuję statyczne twierdzenie, aby upewnić się, że ten warunek jest zawsze sprawdzany. Na koniec, jeśli wierzysz, że Twoje struktura może ewoluować później, nie wahaj się zwiększyć Typ publiczny, aby zachować miejsce dla przyszłych rozszerzeń bez łamania ABI.

Wada : Odlewanie z typu publicznego na prywatny może wywołać ścisłe Ostrzeżenia aliasingowe.

Odkryłem później, że ta metoda ma podobieństwa do struct sockaddr wewnątrz gniazda BSD, co spełnia zasadniczo ten sam problem ze ścisłymi ostrzeżeniami o aliasingu.

 0
Author: Cyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-06 00:44:20