LINUX.ORG.RU

Использование С-шных стуктур нефиксированной длинны в C++

 


0

2

В C есть традиция использования структур нефиксированного размера, когда в конце структуры идет массив, и сколько элементов под него сверх размера базовой структуры зарезервируешь, столько и будет.

typedef struct
{
        int           size;                
        double           p[];
} my_struct_t;

И дальше

my_struct_t*  my_struct = (my_struct_t*) malloc(sizeof(my_struct_t) + sizeof(double) * my_size);

Полный код примера:

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

typedef struct
{
        int           size;                
        double           p[];
} my_struct_t;

int main(void)
{
    int my_size = 5;

    my_struct_t*  my_struct = (my_struct_t*) malloc(sizeof(my_struct_t) + sizeof(double) * my_size);

    my_struct->size = my_size;
    for(int i=0; i<my_size; i++)
    {
      my_struct->p[i] = i;
      printf("i = %i\n",i);
    }
    free(my_struct);
}

Вопрос, вот как бы такую структуру завернуть во что-то максимально С++’ное, чтобы с этим объектом можно было бы взаимодействовать как с объектом из мира C++, чтобы на него delete срабатывал. Или очистка, когда указатель на него перестал существовать и т.п. Возможно чтобы можно было знать настоящий размер структуры для ее копирования.

А в ключевой момент, когда пришло время звать обработчик этой структуры написанный на Си, получить от этого объекта чистый my_struct_t* и позвать нужную сишную функцию.

Есть auto_ptr, емнип. Но он не решает вопрос с сохранением знания о размере структуры.

Есть ли что-то еще, более подходящее?

★★★

А чем тот же std::array не устраивает? Если же динамическое всё, то лучше использовать нормальный вектор КМК. Или такая структура извне подаётся?

WatchCat ★★★★★
()
Последнее исправление: WatchCat (всего исправлений: 1)
Ответ на: комментарий от WatchCat

Или такая структура извне подаётся?

Типа того. Формат структуры строго сишный и определяется во-вне… Ее надо покрутить какое-то время в C++ном коде а потом передать в чисто сишный.

shaplov ★★★
() автор топика
Ответ на: комментарий от shaplov

Тогда можно обернуть банальным классом с конструктором типа cool_class(cool_c_structure* p) и иметь его во все поля как хочется.

upd: Ну и если аллокация происходит в сишечке, то ни в коем случае не использовать плюсовый delete на структуру в деструкторе.

WatchCat ★★★★★
()
Последнее исправление: WatchCat (всего исправлений: 2)

GCC вроде поддерживает VLA для плюсов

struct my_struct_t {
    int size;
    double p[size];
};
SR_team ★★★★★
()

Вопрос, вот как бы такую структуру завернуть во что-то максимально С++’ное, чтобы с этим объектом можно было бы взаимодействовать как с объектом из мира C++, чтобы на него delete срабатывал. Или очистка, когда указатель на него перестал существовать и т.п. Возможно чтобы можно было знать настоящий размер структуры для ее копирования.

Если у тебя структура такого типа выделяется только на хипе, то можно сделать класс, в котором есть указатель на такую структуру, и чтобы при инициализации оно там дергало malloc()

В языке D это можно как-то так сделать: https://wandbox.org/permlink/0sSaVaWn9SSgseOH

SZT ★★★★★
()
Ответ на: комментарий от WatchCat

ни в коем случае не использовать плюсовый delete на структуру в деструкторе.

Определение оператора delete спасет отца русской демократии.

Siborgium ★★★★★
()
Ответ на: комментарий от Siborgium

s/не использовать плюсовый delete/ не использовать дефолтый плюсовый delete/g

В любом случае надо дёргать сишечную функцию иначе получим тот ещё расколбас.

WatchCat ★★★★★
()
Ответ на: комментарий от WatchCat

Тогда можно обернуть банальным классом с конструктором типа cool_class(cool_c_structure* p) и иметь его во все поля как хочется.

Тоже не покатит, таких структур мягко говоря более одной, и каждой писать свою обертку замучаешься… Хочется какую-то единообразную обертку вида

too_smart_ptr<cool_c_structure> my_obj(int size);
too_smart_ptr<cool_c_structure> my_obj(cool_c_structure*, existing_strucure, int size);

Я наверное даже что-то такое и написать смогу. Но луче что-то стандартное…

shaplov ★★★
() автор топика
Ответ на: комментарий от shaplov

Ну так темплейты в помощь. А стандартное в плюсах по отношению к сишечным структурам сложнонатянутое.

WatchCat ★★★★★
()
Последнее исправление: WatchCat (всего исправлений: 1)

Есть auto_ptr, емнип. Но он не решает вопрос с сохранением знания о размере структуры.

auto_ptr уже много лет как протух.

Размер структуры лучше всего хранить в ней самой, или, если это невозможно по каким-то причинам, в простеньком враппере.

Или очистка, когда указатель на него перестал существовать и т.п.

Для структуры из поста достаточно сделать


std::unique_ptr<my_struct_t, decltype(&free)> ptr{ my_struct_ptr_created_with_malloc, &free };
Siborgium ★★★★★
()

Кстати, дергать malloc необязательно, в плюсах можно переопределить оператор new соответствующим образом.

Siborgium ★★★★★
()
Ответ на: комментарий от WatchCat

Одно другому не мешает, и приходящие из сишного кода указатели можно заворачивать в unique_ptr, как показано выше, при этом для созданных в крестах имея чисто крестовый new/delete.

Siborgium ★★★★★
()
    typedef struct
    {
        int size;
        double* p;
    } my_struct_t;

    template<int Size>
    class MyData
    {
    public:
        my_struct_t s;
        double buf[Size];

        MyData()
        {
            s.size=Size;
            s.p=buf;
        }
    };

    MyData<100> many_items;
    some_c_function(&many_items.s)
pathfinder ★★★★
()
Ответ на: комментарий от WatchCat

ТС придумал проблему на пустом месте. Не определившись с ABI это все бессмысленно. Тогда уж лучше заняться измышлениями на тему использования структур из питона в коде на яве.

pathfinder ★★★★
()

Так? Или это слишком банально?

class Wrapper {
public:
    Wrapper(my_struct_t *c) : c(c)
    { }
    Wrapper(const Wrapper &) = delete;
    void operator=(const Wrapper &) = delete;
    ~Wrapper()
    { free(c); }

public:
    int size() const
    { return c->size; }

    double & operator[](int idx)
    { return c->p[idx]; }

private:
    my_struct_t *c;
};
xaizek ★★★★★
()
Ответ на: комментарий от shaplov

а зачем тебе new? создавай через malloc и используй умные указатели со своим делитером, который будет звать free

EugeneBas ★★
()

Можно использовать placement new на буффере, полученном из malloc, чтобы у компилятора не было претензий на лайфтайм объекта.

#include <cstdint>
#include <cstring>
#include <new>

template<typename T>
struct Wrapper
{
    T* internal{nullptr};

    Wrapper(size_t size)
    {
        uint8_t* buf = new uint8_t[size];
        internal = new(buf) T();
    }

    ~Wrapper()
    {
        uint8_t* buf = reinterpret_cast<uint8_t*>(internal);
        internal->~T();
        delete[] buf;
    }
};

struct Test
{
    size_t size;
    double p[];
};

int main()
{
    Wrapper<Test> wrap(sizeof(Test) + sizeof(double) * 10);
    wrap.internal->size = 10;
    wrap.internal->p[2] = 1.0;
}

https://godbolt.org/z/EEbzfa4r4

Moncruist
()
Ответ на: комментарий от Moncruist

М-м-м-м-м....

Больше, чем запах напалма по утрам, я люблю только утечку ресурсов в С++ коде из трёх с половиной строчек.

LamerOk ★★★★★
()
Ответ на: комментарий от LamerOk

Вы всерьёз думаете, что сишные структуры будут кидать исключения? Мой вариант - это всего лишь glorified reinterpret_cast. Можно использовать его вместо placement new, но это будет UB с точки зрения стандарта, хоть и будет работать.

Moncruist
()
Ответ на: комментарий от LamerOk

Гарантию давал ТС в постановке задачи, ведь он собирался эти структуры в сишные функции передавать. Но если хочется, чтобы и с исключениями всё прекрасно работало, то тут как обычно есть много вариантов. Можно концепт навернуть на параметр шаблона, проверяющий на то, чтобы конструктор был noexcept, можно в try/catch блок обернуть placement new, а можно placement new с nothrow вызывать и делать std::terminate если вернёт nullptr.

Moncruist
()

double p[1]; написать в структуре, и код будет корректен для c++.

m0xf
()

В C есть традиция использования структур нефиксированного размера, когда в конце структуры идет массив

Зачем так, кстати, делают? Объясните для не знающего.

snake266 ★★
()
Ответ на: комментарий от snake266

В C есть традиция использования структур нефиксированного размера, когда в конце структуры идет массив

Зачем так, кстати, делают? Объясните для не знающего.

Ну это удобный способ получить честный объект содержащий массив переменной длинны существущий прямо как часть объекта, и к которому можно удобно обращаться используя понятные конструкции языка… просто пиасать obj->p[17] и получить заказанный элемент массива без извратов.

Так в постгрессе как минимум сделано.

shaplov ★★★
() автор топика
Ответ на: комментарий от snake266

Одна аллокация вместо двух. Локальность данных. Меньше pointer indirection.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)
Ответ на: комментарий от shaplov

Я держал для этого указатель, вроде такое же обращение без извратов, разве что главное не забыть освободить память.

@Siborgium
А тогда понятно, чем это хуже моего указателя на массив

snake266 ★★
()
Ответ на: комментарий от snake266

Я держал для этого указатель, вроде такое же обращение без извратов, разве что главное не забыть освободить память.

@Siborgium лучше меня объяснил…

Дело в том, что в постгресе есть механизм специальной обертки для хранимых в памяти объектов переменной длинны. И если ты сумел впихнуть данные в одну структуру то оборачиваешь эту структуру в это обертку и далее можешь оперировать с этим объектом с разным уровнем абстракции в зависимости от ситуации. А вот с двусостовным объектом такой фокус уже не получится.

shaplov ★★★
() автор топика

У меня в общем и целом сердце успокоилось на следующем решении…

Я во-первых завел объект «указатель с размером»

template<class T> class sized_ptr
{
  private:
    T* _ptr{nullptr};
    size_t _size;
  public:

    operator T*() const {return _ptr;};
    size_t size() {return _size;};
    sized_ptr(T* ptr, size_t size): _ptr{ptr}, _size{size} {};
    ~sized_ptr() {if (_ptr) free(_ptr);};
};

Для более абстрактной работы с подобного рода объектами, не ограничивающейся только ситуацией когда в конце добавлен массив, но и предустматривающий более кривые случаи.

А для случая когда у нас конкретно структура с массивом переменной длинны в конце, то для этого я создал объект pointer to Variable Length Array Terminated Object: VLATO_ptr. Ему надо в параметрах шаблона сообщить тип самого объекта и тип элемента массива в конце этого объекта.

Так же не надо оперировать понятием «размер структуры», а можно пользоваться понятием «размер массива»

template<class T, class ArrayT> class VLATO_ptr
{
  private:
    T* _ptr{nullptr};
    size_t _length;
  public:

    operator T*() const {return _ptr;};
    operator sized_ptr<T>() {sized_ptr<T> res(_ptr,size()); _ptr=NULL; return res;};

    size_t length() {return _length;};
    size_t size()   {return sizeof(T) + _length * sizeof(ArrayT);};
    VLATO_ptr(size_t length);
    VLATO_ptr(T* ptr, size_t length): _ptr{ptr}, _length{length} {};
    ~VLATO_ptr() {if (_ptr) free(_ptr);}
};


template<class T, class ArrayT>
VLATO_ptr<T,ArrayT>::VLATO_ptr(size_t length)
{
  _ptr = (T*) malloc(sizeof(T) + sizeof(ArrayT) * length);
  _length = length;
}

В результате создаешь объект

   VLATO_ptr<my_struct_t, double> vlato10(10);

Заполняешь его

   ((my_struct_t*) vlato10)->size = 10;
   ((my_struct_t*) vlato10)->p[3] = 11;

А после заполнения хранишь как sized_ptr

sized_ptr<my_struct_t> szo10 = vlato10;

пока не понадобиться скормить его сишному вызову. Тогда из него извлекаешь обычный указатель.

my_struct_t*  ps10 = szo10;
shaplov ★★★
() автор топика
Последнее исправление: shaplov (всего исправлений: 4)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.