• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

Instancjonowanie w C++ struktury z Flexible Array Member (FAM)

Object Storage Arubacloud
0 głosów
753 wizyt
pytanie zadane 1 lipca 2019 w C i C++ przez draghan VIP (106,230 p.)

Cześć!

W jaki sposób poprawnie dogadać się w kodzie C++ z tzw. Flexible Array Member ze standaru C?

API z C oczekuje ode mnie wypełniania struktur z takimi paskudami i muszę jakoś to obsłużyć.

Miałem na początku zagwozdkę, jak w ogóle podejść do tematu: jak stworzyć i przypisać wartości do obiektu takiej struktury?
Wykoncypowałem na tę chwilę taki wrapper, który zajmuje się podstawowym zarządzaniem pamięcią dla tego ustrojstwa, ale nie mam przekonania, czy nie dotykam Undefined Behavior przy dostępie do elementów takiej tablicy.

Przykład:

// main.cpp

#include "FlexibleArrayMemberWrapper.hpp"

extern "C"
{

// structure with FAM from C API:
typedef struct
{
    double ordinary_member_d;
    int flexible_array_member[];
} c_struct_with_fam;

}

int main()
{
    c_struct_with_fam bar;
    bar.ordinary_member_d = 3.14;

    // bar.flexible_array_member = new int[3]; // (1)

    FlexibleArrayMemberWrapper<c_struct_with_fam, int> foo{3};
    foo.get().ordinary_member_d = 3.14;

    foo.get().flexible_array_member[0] = 100; 
    foo.get().flexible_array_member[1] = 200; // (2)
    foo.get().flexible_array_member[2] = 300;
}
// FlexibleArrayMemberWrapper.hpp

#pragma once

#include <cstdlib>
#include <new>

template <typename struct_t, typename array_t>
class FlexibleArrayMemberWrapper
{
public:
    FlexibleArrayMemberWrapper(const FlexibleArrayMemberWrapper &) = delete;
    FlexibleArrayMemberWrapper(FlexibleArrayMemberWrapper &&) = delete;
    void operator=(const FlexibleArrayMemberWrapper &) = delete;

    explicit FlexibleArrayMemberWrapper(size_t numberOfElements)
            : data{nullptr}
    {
        const auto memory = std::malloc(getSize(numberOfElements));
        if (memory == nullptr)
        {
            throw std::bad_alloc{};
        }
        this->data = reinterpret_cast<struct_t *>(memory);
    }

    ~FlexibleArrayMemberWrapper()
    {
        if (data != nullptr)
        {
            std::free(data);
        }
    }

    struct_t &get()
    {
        return *this->data;
    }

private:
    struct_t *data;

    constexpr static size_t getSize(size_t numberOfElements)
    {
        constexpr auto structureTypeSize = sizeof(struct_t);
        constexpr auto arrayTypeSize = sizeof(array_t);
        return arrayTypeSize * numberOfElements + structureTypeSize;
    }
};


(1) - to było moje pierwsze naiwne podejście do przypisania wartości dla FAM
(2) - trochę mi to pachnie UB

Czy ktoś miałby może jakieś porady w tej kwestii, spotkał się z takim zapotrzebowaniem? Wszystko co udało mi się znaleźć traktuje o propozycjach dorzucenia FAM do standardu C++.

komentarz 1 lipca 2019 przez j23 Mędrzec (194,920 p.)
Dlaczego UB?

Jak dla mnie, kod wygląda OK.
komentarz 1 lipca 2019 przez adrian17 Ekspert (344,860 p.)
Tak jak formalnie C++ tutaj nic ciekawego nie powie (na pewno dostaniesz krzyki przy -Wpedantic), tak nie spodziewam się by kompilatory i sanitizery nie miały problemu.

(BTW, z tego co wiem to ten extern "C" przy definicji struktury chyba nic nie zrobi?)
komentarz 2 lipca 2019 przez draghan VIP (106,230 p.)

na pewno dostaniesz krzyki przy -Wpedantic

Przy standardowym zestawie:

-Wall -Wduplicated-cond -Wformat=2 -Weffc++ -Wdouble-promotion -Wuseless-cast -Wnull-dereference -Wlogical-op -Wduplicated-branches  -Wmisleading-indentation -Wsign-conversion -Wpedantic -Wconversion -Woverloaded-virtual -Wunused -Wextra -Wshadow -Wnon-virtual-dtor -pedantic -Wold-style-cast -Wcast-align

dostaję tylko warn pokazujący na linijkę 12. pliku main.cpp, mówiący o tym że FAM jest featurem C (GCC i Clang).

BTW, z tego co wiem to ten extern "C" przy definicji struktury chyba nic nie zrobi?

Z tego co mi wiadomo, to działanie widoczne jest dla konwencji wołania funkcji i manglowania nazw zmiennych, czyli chyba nic nie powinno zmienić w definicji struktury; tutaj chciałem tylko zaznaczyć że dany fragment otrzymuję w spadku z API C.

Dlaczego UB?

Jak dla mnie, kod wygląda OK.

Właśnie problem jest tego typu, że sam nie wiem. :D Kod wydaje się działać w GCC i MS VS C++, ale nie znaczy to wcale że jest poprawny - stąd moje pytanie czy nie zrobiłem jakiegoś (oczywistego lub mniej oczywistego) błędu.

komentarz 2 lipca 2019 przez j23 Mędrzec (194,920 p.)

Jeśli dobrze pamiętam, stare wersje VC nie pozwalały na takie tablice, dlatego struktury takie jak na przykład BITMAPINFO czy SHITEMID zawierają tablice, które mają jeden element. To tak nawiasem.

1 odpowiedź

0 głosów
odpowiedź 2 lipca 2019 przez mokrowski Mędrzec (155,460 p.)
edycja 2 lipca 2019 przez mokrowski

Przy FAM alokujesz pamięć dynamicznie dla całości struktury tak że tablica rezyduje na końcu struktury w ciągłej pamięci (najpierw ordinary_member_d a później ciągłe bajty z flexible_array_member[]).U Ciebie tak nie jest. Tak delete dla nullptr jak i free dla NULL , ma gwarancję "nie robienia niczego" https://linux.die.net/man/3/free "If ptr is NULL, no operation is performed." (koniec 2 akapitu).

komentarz 2 lipca 2019 przez draghan VIP (106,230 p.)

Przy FAM alokujesz pamięć dynamicznie dla całości struktury tak że tablica rezyduje na końcu struktury w ciągłej pamięci (najpierw ordinary_member_d a później ciągłe bajty z flexible_array_member[]).U Ciebie tak nie jest.

Czy mógłbyś rozwinąć, dlaczego u mnie tak nie jest? Wykonuję jedną alokację dla całości danych, zatem jest to pamięć ciągła, w której siedzą normalne składowe struktury + jest na końcu bufor dla danych tablicy.

komentarz 2 lipca 2019 przez mokrowski Mędrzec (155,460 p.)
edycja 2 lipca 2019 przez mokrowski

Spoko, zwracam honor, moje nieuwaga i przeoczenie :)

Nie ma UB w kodzie który podałeś. Oprócz "nieładnego" reinterpret_cast które i tak każdy by dopuścił na styku z C z takim "potworem" jakim jest FAM.

Z ciekawości... co za aplikacja (jakiej klasy) tak definiuje API?

Takie coś mi jeszcze przyszło do głowy:

// FlexibleArrayMemberWrapper.hpp

#pragma once

#include <cstdlib>
#include <memory>

template <typename struct_t, typename array_t>
class FlexibleArrayMemberWrapper
{
public:
    FlexibleArrayMemberWrapper(const FlexibleArrayMemberWrapper &) = delete;
    FlexibleArrayMemberWrapper(FlexibleArrayMemberWrapper &&) = delete;
    void operator=(const FlexibleArrayMemberWrapper &) = delete;


    explicit FlexibleArrayMemberWrapper(size_t numberOfElements)
            : table_size{numberOfElements},
              data{reinterpret_cast<struct_t *>(std::malloc(getSize(numberOfElements)))}
    {
        if (! data)
        {
            throw std::bad_alloc{};
        }
    }
    // FIXME: to wymaga przemyślenia czy oddawać surowy wskaźnik czy raczej
    // opakować dostępy do tablicy przez operator[] i bardziej hermetyzować.
    // Tu nie mam odpowiedzi bo zależy od kontekstu.
    struct_t &get() const
    {
        return *this->data.get();
    }

    const size_t table_size;
private:
    const std::unique_ptr<struct_t> data;

    constexpr static size_t getSize(size_t numberOfElements)
    {
        constexpr auto structureTypeSize = sizeof(struct_t);
        constexpr auto arrayTypeSize = sizeof(array_t);
        return arrayTypeSize * numberOfElements + structureTypeSize;
    }
};

PS1. Ukryłem bo w trybie wakacyjnym nie miałem dostępu do kompilatora a chciałem sprawdzić koncepcję.

PS2. Już kolega odpowiedział mi na pytanie... pewnie Python. Ale jeśli Python, to chyba nie ma sensu kopać się z koniem tylko użyć np. Boost.Python.

komentarz 2 lipca 2019 przez draghan VIP (106,230 p.)

Przepraszam że odniosłem się tylko do fragmentu odpowiedzi, ale w pracy nie miałem za bardzo czasu na pisanie.

Tak delete dla nullptr jak i free dla NULL , ma gwarancję "nie robienia niczego" https://linux.die.net/man/3/free "If ptr is NULL, no operation is performed." (koniec 2 akapitu).

Zgadza się, ale to akurat pozwoliłem sobie uznać za nieistotne (podobnie jak np. usunięty konstruktor przenoszący czy kopiujący, które tak naprawdę mogłyby istnieć, bo dlaczego nie).

(mam odpowiedzi do ukrytego komentarza, ale dopóki nie uznasz że chcesz go odkryć, zostawię je dla siebie :)

komentarz 2 lipca 2019 przez draghan VIP (106,230 p.)

Spoko, zwracam honor, moje nieuwaga i przeoczenie :)

Ok, spoczko. Nie ma czego zwracać. :D Przez chwilę wpadłem w panikę że dzieją się tutaj jakieś rzeczy zupełnie poza moim rozumowaniem.

Z ciekawości... co za aplikacja (jakiej klasy) tak definiuje API?

(...)

PS2. Już kolega odpowiedział mi na pytanie... pewnie Python. Ale jeśli Python, to chyba nie ma sensu kopać się z koniem tylko użyć np. Boost.Python.

Pewna niskopoziomowa biblioteka (czyste C, bez pytonów czy innych gadów) z pogranicza firmware'u, szczegółów niestety nie mogę zdradzić z uwagi na wiążące mnie umowy.

// FIXME: tu wymaga przemyślenia czy oddawać surowy wskaźnik czy raczej
// opakować dostępy do tablicy przez operator[] i bardziej hermetyzować.
// Tu nie mam odpowiedzi bo zależy od kontekstu.

Sam się zastanawiałem nad tym. Ale w jaki sposób zapewnić generycznie dostęp do pozostałych (normalnych) składowych struktury owrapowanej tym?
Fajnie by się tutaj sprawdziły metaklasy; w połączeniu ze statyczną refleksją nie trzeba byłoby nawet podawać drugiego argumentu szablonu, wystarczyłoby samo podanie typu struktury i można byłoby wyjąć typ ostatniego membera struktury... Ale to pieśń przyszłości, ja mam dostępne "jedynie" C++17. :)

A poza tym w zupełności wystarczy mi wskaźnik, bo wszystko co muszę zrobić to wypełnić pola struktury i podać wskaźnik na instancję do funkcji z tego API.

PS1. Ukryłem bo w trybie wakacyjnym nie miałem dostępu do kompilatora a chciałem sprawdzić koncepcję.

Spoczko, podejrzewałem coś w tym guście. Miłego wypoczynku.

PS
Celowo zniknąłeś destruktor?

komentarz 2 lipca 2019 przez mokrowski Mędrzec (155,460 p.)

Usunąłem destruktor bo dodałem const std::unique_ptr na dane. Innym pytaniem jest to czy jest to zasadne w świetle zwracania surowego wskaźnika na dane. No ale to napisałem w komentarzu.

W kompilatorach C masz często dodane rozszerzenia offsetoff (a jak nie masz to sobie zrób https://en.wikipedia.org/wiki/Offsetof) oraz typeof ( https://gcc.gnu.org/onlinedocs/gcc/Typeof.html ). Oczywiście to są rozszerzenia (gcc, icc, clang ma). Intensywnie z tych właściwości korzysta GNU/Linux. Z samego założenia w FAM jedynie ostatni element struktury może być doprecyzowany co do rozmiaru w czasie alokowania.

komentarz 3 lipca 2019 przez draghan VIP (106,230 p.)

Ach, nie zauważyłem tej drobnej zmiany. Użyłem pary malloc / free ze względu na taką błahą formalność:

std::unique_ptr wykonuje domyślnie delete-expression na swoim wskaźniku, a delete-expression jest UB gdy pointer nie pochodzi z new-expression, zaś malloc nie kwalifikuje się jako new-expression; opakowanie pointera w unique_ptr i dodanie deletera uznałem za równie dobre jak wrzucenie destruktora. ¯\_(ツ)_/¯

komentarz 3 lipca 2019 przez mokrowski Mędrzec (155,460 p.)
Spoko... stąd też napisałem że "takie coś mi do głowy przyszło" bo o pomysły pytałeś. Zastanawiam się czy ten wrapper nie powinien być builderem... bo tak naprawdę to taki przypadek.

Podobne pytania

0 głosów
1 odpowiedź 192 wizyt
pytanie zadane 19 stycznia 2023 w C i C++ przez krzysztof.polak Nowicjusz (120 p.)
0 głosów
2 odpowiedzi 231 wizyt
pytanie zadane 19 listopada 2022 w C i C++ przez Billy Użytkownik (680 p.)
0 głosów
1 odpowiedź 465 wizyt
pytanie zadane 22 września 2016 w C i C++ przez niezalogowany

92,551 zapytań

141,393 odpowiedzi

319,523 komentarzy

61,936 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...