Nakładka na C

Odpowiedz Nowy wątek
2016-01-21 14:18
Pawel TS
0

Witam!

W pewnych momentach C jest dość irytujące, w szczególności że na niektóre platformy jest taki kompilator i koniec.

Postanowiłem stworzyć pewne nakładki, najpierw zabrałem się za nowy typ w C.
Nowy typ jest szablonem, który dodatkowo zawiera informacje o wielkości, nazwie, typie.

#include <stdio.h>
#include <string.h>

// szablon nowego typu
typedef struct{
    size_t        size;
    char        * name;
    char        * type;
    unsigned char data[];
} var_template;

// funkcja ktora prezentuje jak mozna wykorzystac nowy typ
void var_dump(void *ptr){
    int i;
    var_template * t = (var_template*) ptr;
    printf("%s\n", __FUNCTION__);
    printf(" name: %s\n",  t->name);
    printf(" type: %s\n",  t->type);
    printf(" size: %lu\n", t->size);
    printf(" dump: ");

    for(i=0; i<(int)t->size; ++i){
        printf("%02x ", t->data[i]);
    }
    printf("\n");
}

// tworzenie nowego typu
#define TO_STR2(x) #x
#define TO_STR(x) TO_STR2(x)

#define var(TYPE, NAME) \
    struct{ \
        size_t       size; \
        char      *  name; \
        char      *  type; \
        typeof(TYPE) data; \
    } NAME = { \
        .size = sizeof(TYPE), \
        .name = TO_STR(NAME), \
        .type = TO_STR(TYPE), \
    };

// missing initializer for member ... data

#define container_of(ptr, type, member) ({ \
                const typeof( ((type *)0)->member ) *__mptr = (ptr); \
                (type *)( (char *)__mptr - offsetof(type,member) );})

//#define var_data(TYPE, PTR) (TYPE)(((var_template*)PTR)->data)

#define var_data(TYPE, PTR) ({ \
    const var_template* t = (var_template*) PTR; \
    strcmp(t->type, TO_STR(TYPE)) == 0 ? (TYPE*) t->data : 0; \
    })

struct my_struct{
    char a;
    char b;
};

// przyklad 2
void my_struct_print(void * ptr){
    struct my_struct * s = var_data(struct my_struct, ptr);
    if(s)
        printf("%s: %d, %d\n", __FUNCTION__, s->a, s->b);
    else
        printf("%s: error\n", __FUNCTION__);
}

int main(){

 // przyklad nakladki na int
    printf("\nNakladka na int\n");
    var(int, foo);
    foo.data = 0x12345678;
    var_dump(&foo);
    my_struct_print(&foo);

 // przyklad nakladki na strukture
    printf("\nNakladka na struct my_struct\n");
    var(struct my_struct, s);
    s.data.a = 1;
    s.data.b = 2;
    var_dump(&s);
    my_struct_print(&s);

    return 0;
}

Wynik:

Nakladka na int
var_dump
 name: foo
 type: int
 size: 4
 dump: 78 56 34 12 
my_struct_print: error

Nakladka na struct my_struct
var_dump
 name: s
 type: struct my_struct
 size: 2
 dump: 01 02 
my_struct_print: 1, 2

Pytania:

  1. Czy uda się zmanipulować tak "#define" by deklarowanie zmiennych wyglądało tak:

    var(int)  foo;

    ??
    Bardzo przeszkadza sposób deklaracji struktury razem z danymi:

    struct{
    int a;
    } name = {
    .a = 12,
    };

    Gdyby nazwa była na końcu, nie byłoby problemu.

  2. missing initializer for member ... data
    W nie mogę nic wpisać, ponieważ są to różne typy, ale kompilator swoje.
    Da się jakoś ominąć ten warning?
    Jakiś sposób, aby jednak coś wpisać?
    Może jakoś wyłączyć warninga przy tworzeniu struktury i później włączyć z powrotem?

  3. Jakieś dodatkowe pomysły, sugestie?

Czeka mnie jeszcze dużo pracy i mile wiedziane podpowiedzi ;)

Pozostało 580 znaków

2016-01-21 14:31
Pawel TS
1

Dobra, mam rozwiązanie na pkt. 2

#define var(TYPE, NAME) \
    struct{ \
        size_t       size; \
        const char * name; \
        const char * type; \
        typeof(TYPE) data; \
    } NAME; \
    NAME.size = sizeof(TYPE); \
    NAME.name = TO_STR(NAME); \
    NAME.type = TO_STR(TYPE);

Pozostało 580 znaków

2016-01-21 14:38
Pawel TS
1

Dobra, mam rozwiązanie na pkt 1 ;)

#define JOIN(a,b) a ## b
#define JOIN_HELPER(a,b) JOIN(a,b)
#define MAKE_UNIQ(a) JOIN_HELPER(a, __LINE__)

#define var_helper(TYPE, NAME) \
    struct{ \
        size_t       size; \
        const char * name; \
        const char * type; \
        typeof(TYPE) data; \
    } NAME; \
    NAME.size = sizeof(TYPE); \
    NAME.name = TO_STR(NAME); \
    NAME.type = TO_STR(TYPE); \
    typeof NAME

#define var(TYPE) var_helper(TYPE, MAKE_UNIQ(var_))

I teraz:

var(int) moj_int;

Pozostało 580 znaków

2016-01-21 14:41
Pawel TS
0

Cofam pkt 1. Błędów kompilacji nie ma ale program źle działa.

Pozostało 580 znaków

2016-01-21 14:42
6

Ja sie zapytam.

Po co? Masz jakis zyciowy przyklad (a nie, ze jest irytujace) gdzie rzeczywiscie to sie przyda?
Nie wydumany wyimaginowany przyklad (w ktorym np nie mozna uzywac niczego oprocz C z roku 1986) tylko taki prawdziwy.

Bo troche sensu nie widze w tym co robisz i chcialbym miec to oslenienie "aahhh dlatego to jest fajne"

chyba ze robisz sztuka dla sztuki to wtedy co innego

edytowany 1x, ostatnio: fasadin, 2016-01-21 14:43

Pozostało 580 znaków

2016-01-21 15:50
Pawel TS
0

Sprawa jest dość złożona i skomplikowana, głównie chodzi o zdalne wirtualne mapowanie pamięci urządzenia po jakimś protokole.

Nie chcę rozpoczynać dyskusji gdzie i jak to wykorzystać, po prostu implementuje mechanizmy z C++ do C.

Dam prosty przykład:

Ogólnie tam gdzie zwylke przekazuje się struktury:

void foo(&my_struct, sizeof(my_struct));

Problem się pojawia, gdy trzeba podać jeszcze dodatkowe dane:

void foo(0x100005, &my_struct, sizeof(my_struct));

Gdzie adres 0x100005 może być liczony na podstawie użytej struktury (dana struktura na stałe jest przypisana do danego adresu - mechanizm mam już opracowany).

struct a_t a;
/* ... */

struct b_t b;
/* ... */

struct c_t c;
/* ... */

compress_and_send(&a, &b, &c);

// zamiast

compress_and_send(0x100005, &a, sizeof(a), 0x100010, &b, sizeof(b), 0x100015, &c, sizeof(c));

gdzie:

void compress_and_send(void * pack1, ...);

Dodatkowo, ta sama funkcja może także różnie przetwarzać dane na podstawie typu danych przekazanych przez argument.

Taki mechanizm jest bardzo znany w cpp, sama klasa może zawierać w sobie podstawowe informacje o sobie.
Są templaty które zmniejszają naparzanie kodu.
Czemu nie zrobić tego samego w C?

#include <stdio.h>
#include <var.h>

void log(void * ptr){
    struct my_struct * _struct = var_data(struct my_struct, ptr);
    if(_struct){
        printf("logbook: %s %d, %d\n", var_type(ptr), _struct->a, _struct->b);
        return;
    }

    int * _int = var_data(int, ptr);
    if(_int){
        printf("logbook: %s 0x%x\n", var_type(ptr), *_int);
        return;
    }
    printf("logbook: unknown type\n");
}

int main(){
    var(int, foo);
    foo.data = 0x12345678;

    var(struct my_struct, str);
    str.data.a = 1;
    str.data.b = 2;

    log(&foo);
    log(&str);
    return 0;
}
logbook: int 0x12345678
logbook: struct my_struct 1, 2

Można zadać pytanie, ale po co pisać w C++ skoro ten sam efekt będzie w C?
Po co nakładki zrobione przez QT na C++?

Doskonale zdaję sobie sprawe, że ktoś kto jest ekspertem w C, po zobaczeniu kodu powie WTF, ale to jest normalna sprawa jak bierze się nowe biblioteki czy narzędzia.

W skrócie, rozbudowanie C o większe możliwości programowania uogólnionego.
Jeśli komuś to nie przemawia, uznajmy to sztuka dla sztuki.

Przesłane przeze mnie kody nie są ostateczne, to na razie wklepany koncept.

Pozostało 580 znaków

2016-01-21 16:06
kq
1

Nie wiem co ma Qt do tego, ale nie możesz po prostu użyć C++ zamiast wydziwiać?


Pozostało 580 znaków

2016-01-21 17:42
Pawel TS
1

Na pewne platformy nie mam kompilatorów c++ :(

Jest urządzenie które ma pewną przestrzeń pamięci i na nią jest nałożona struktura, np:

// inty są przykładem, oryginalna struktura zawiera ponad 1k różnych pól w postaci struktur, intów i innych
struct my_struct_t{
    int a;
    int b;
    int c;
    int d;
};

Aby dało się zasymulować problem, stwórzmy statyczną strukturkę, oraz funkcję która się wykona jeśli przyjdą dane.

static struct my_struct_t memory;

void recv(int addr, const void * data, int len){
    memcpy((uint8_t*)&memory+addr, data, len);
}

Funkcja uaktualnia dane w "memory"

Teraz pytanie, co zrobić po stronie "klienta" który chce dane pole zmienić.

  1. Bierzemy i kopiujemy strukturę do klienta
  2. Wybieramy pole które chcemy zmodyfikować:
    typeof(((struct my_struct_t*)0)->b) field;
  3. Wypełniamy:
    field = 12;
  4. Wysyłamy:
    
    send((size_t)(&((struct my_struct_t*)0)->b), &field, sizeof(field));

// gdzie:
void send(int addr, const void * data, int len);


Symulacja:
```cpp
#include <stdio.h>
#include <stdint.h>
#include <string.h>

// wspolna struktura
struct my_struct_t{
    int a;
    int b;
    int c;
    int d;
};

// klient
void recv(int addr, const void * data, int len);

void send(int addr, const void * data, int len){
    recv(addr, data, len);
}

int main(){
    typeof(((struct my_struct_t*)0)->b) field;
    field = 12;
    send((size_t)(&((struct my_struct_t*)0)->b), &field, sizeof(field));

    return 0;
}

// serwer
static struct my_struct_t memory;

void recv(int addr, const void * data, int len){
    memcpy((uint8_t*)&memory+addr, data, len);
    printf("%s test: %d\n", __FUNCTION__, memory.b);
}

Dzięki nakładce będę w stanie zapisać to tak:

Serwer:

void recv(void * ptr){
  memcpy((uint8_t*)&memory + var_addr(ptr), var_data(ptr), var_size(ptr));
}

Klient:

// idea
var(my_struct.a) a;

a = 12;

send(a);

// gdzie:
void send(var_type * ptr);

Jeśli chcę obsłużyć inne pola to po prostu schemat jeden:

var(sciezka_do_elementu_struktury) field;

// wypelnienie danych //

send(field);

Pomysł zrealizowałem i działa poprawnie.
Opakowałem dodatkowe informacje do struktury i tak powstała nakładka "var".

Bez takiej nakładki można równie dobrze wywalić z kernela container_of i zapisywać coś ala:

#include <stdint.h>
#include <stdio.h>

struct numbers {
    int one;
    int two;
    int three;
};

#define offsetof(st, m) ((size_t)(&((st *)0)->m))
#define container_of(ptr, type, member) ({ \
                const typeof( ((type *)0)->member ) *__mptr = (ptr); \
                (type *)( (char *)__mptr - offsetof(type,member) );})
int main(){

    struct numbers n;

    int *ptr = &n.two;
    struct numbers *n_ptr;

    //n_ptr = container_of(ptr, struct numbers, two);
    n_ptr =  ({const typeof( ((struct numbers *)0)->two ) *__mptr = (ptr);(struct numbers *)( (char *)__mptr - ((size_t)(&((struct numbers *)0)->two)) );});
}

Czy nakładka "var" jest wydziwianiem... no nie wiem...

@Endrju moze Ty zrozumiesz wiecej niz ja ;) - fasadin 2016-01-21 19:03
@fasadin: ja nie chcę tego rozumieć. ;-) - Endrju 2016-01-21 20:41

Pozostało 580 znaków

2016-01-21 19:26
0

Koncepcja ciekawa ale:
a. gdzie Ci C++ brakuje? Serio, jestem ciekaw na jakich platformach.
b. debugowanie i utrzymywalność tego może być żadna...
c. nie lepiej opakować wszystko w structy, np. typedef struct { int a; } int_a_t a potem objechać wszystko makrem _Generic z C11?

Pozostało 580 znaków

2016-01-21 20:02
Pawel TS
0

Kolejny działający motyw:

Pobranie dowolnego "pola" z memory

void get(void * ptr){
  memcpy(ptr, (uint8_t*)&memory + var_addr(ptr), var_size(ptr));
}

// usage:

var(my_struct.a) a;
get(a);

Czyli tworzę sobie pole "a" ze struktury, mój dodatek tworzy strukturę, gdzie zapamiętuje offset "a" względem struktury my_struct i inne bajery.
Adres ten można pobrać poprzez var_addr.
Ponadto "ptr" czy "a" jest wskaźnikiem na pole, które nie zawiera danych dodatku "var". Rozwiązanie podobne jak kernelowy container_of.
Dane przechowywane są pod adresem &a - sizeof(var_template).

Dodałem także sygnature, aby sprawdzić czy dany typ jest typem "var"

#define var_type void*
#define var_signature 0xD21A
typedef struct{
    uint16_t      signature;
    size_t        size;
    const char  * name;
    const char  * type;
    unsigned char data[];
} var_template;

#define var_container(ptr) \
    container_of((typeof(unsigned char[0])*)ptr, var_template, data)

#define var_test(ptr) \
    (var_container(ptr)->signature == var_signature)

void var_dump(var_type ptr){
    printf("%s\n", __FUNCTION__);
    if(!var_test(ptr)){
        printf("error\n");
        return;
    }

    printf(" type: %s\n", var_type(ptr));
    printf(" size: %lu\n", var_size(ptr));
}

// test:
var(int) a; // przy "var" wszystkie deklaracje beda wskaznikami
var_dump(a); // OK

int b;
var_dump(&b); //error

Przykład zestawu gdzie nie ma c++ (chyba że czegoś nie wiem) to xc8, xc16 - kompilatory procesorów rodziny microchip.
Są też inne rodziny uC gdzie nie ma c++.

Na forum piszę:

var(TYP) NAZWA;

Ponieważ jest to czytelne i taki efekt chciałbym uzyskać, póki co mam:

var(TYP, NAZWA);

Pozostało 580 znaków

2016-01-21 20:51
1
  1. Naprawdę chcesz nosić ze sobą tyle danych na procesory tak ograniczone, że nikt oficjalnie nie rzucil dla nich kompilatorem c++?

  2. Kompilacja c++ do c i nagle wszystko łyka jak pelikan

Nie żeby coś, ale jest jakiś kompilator C++ do C poza CFront? - kq 2016-01-21 20:52
@kq nigdy się tym nie bawiłem, ale pewnie celowałbym w llvm - spartanPAGE 2016-01-21 20:56
No ale to raczej do LLVM IR. Interesowałem się tym jakiś (dłuższy) czas temu i z kompilatorów C++ do nie-kodu maszynowego widziałem tylko CFront (RIP 1992), emscripten i jakiś konwerter binarek z MIPSa do JVMa. Że da się - wiadomo. Ale nie widziałem tego nigdy ;) - kq 2016-01-21 20:58
@kq no tak, ale (z tego co patrzę) to z llc można to machnąć to jako C++ -> LLVM IR -> C; llvm-g++ -emit-llvm x.cpp -o program.bc -c -> llc -march=c program.bc -o program.c - spartanPAGE 2016-01-21 21:04
Kiedyś SasC tak kompilował, ale to był czas, gdy C++ było C z klasami. - kaczus 2016-01-21 22:07

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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