LINUX.ORG.RU

Несколько flexible array member

 ,


0

2

Почему не добавят в стандарт, и какие приемы используют что бы реализовать это?

Описание того что я хочу:

struct String {
  int length;
  char str[length]; // == char str[];
                    // указание длинны, просто подсказка для компилятора
};

struct Item {
  struct String name; 
  struct String description;
  bool flag1, flag2, flag3;
  int elements_length;
  struct Element elements[elements_length]
};


// Item *i = malloc(999999999);
// (char*)&i->description.length == ((char*)&i->name.str) + i->name.length

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

★★★★

Ответ на: комментарий от bugfixer

В целом стиль не нравится, постоянные ломки ABI по крайней мере в GNU реализации и жутко длинные шаблоны для даже самых элементарных вещей, приводящие отладку под gdb или разгребание ошибок линковки в сущий ад. В общем если коротко - STL хочет быть универсальным, но из-за этого становится слишком сложным.
Код использующий банальный std::string, линкуемый с библиотекой собранный с другой версией стандарта или другим компилятором может развалиться даже если использовал одни и те же gnustl хидеры. Может подход как в rust бы помог, когда весь код на rust всегда собирается статически одним компилятором, а динамические API только в сишном стиле с unsafe. Опять же если бы STL и boost были бы всегда только header-only - было бы сильно лучше, но это не отменяет взрыва шаблонов под отладчиками и проблем при использовании STL классов в API. Один ожидает функию у которой basic_string один параметр, другой экспортирует - два, в итоге они не линкуются
Наверно если в целом остановится на версии из какого-нибудь с99 или tr1, то у меня претензии только к стилю, но сейчас туда половину буста запихали и запихают ещё, делая написание кода не легче, а сложнее

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

Прототип, на котором видно, что происходит в разных ситуациях. Например, как происходит доступ к элементу массива по индексу, что будет если поменять длину поля в каком-то из элементов, и тому подобное. Другими словами, нужно убрать магию из всех мест, где сейчас в твоей идее находится магия.

i-rinat ★★★★★
()
Ответ на: комментарий от mittorn

постоянные ломки ABI

Масштаб катастрофы явно преувеличен - на моей памяти такое произошло один раз за последние лет 10. И по моей информации один из «власть имущих» сказал «never ever again», время покажет.

с другой версией стандарта или другим компилятором может развалиться

Что есть - то есть. Лучше так не делать.

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

Можно было бы сделать перегружаемые операторы sizeof и offsetof. Тогда доступ к полю элемента был бы согласно перегруженному offsetof и это решает всю «магию». Только я не совсем понимаю, какой синтаксис должен быть у перегрузки offsetof - ведь в этом макросе передаётся имя элемента в виде идентификатора. Возможно что-то вроде:

size_t operator offsetof(field3)() const
{return offsetof(field2) + field1;}

где тело offsetof будет являться частью имени оператора с соответствующей семантикой.
Тогда если sizeof не константный - то в целом объект ведёт себя как объект с flexible array member - нельзя использовать нигде, где требуется статическая аллокация и где sizeof используется в статическом контексте, зато вокруг этого всего смогут работать всякие сериализаторы и динамические аллокаторы просто получая размеры в рантайме.
Так же такой оператор может генерироваться автоматически если размер элемента field[] в структуре задан как зависимость от другого элемента в структуре: char field2[field1];

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

Масштаб катастрофы явно преувеличен - на моей памяти такое произошло один раз за последние лет 10. И по моей информации один из «власть имущих» сказал «never ever again», время покажет.

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

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

компилятор обновлять нельзя?

Можно, но очень очень осторожно. Кроме шуток. Очень много чего вылезти может, начиная от sleepers в Вашем собственном коде. И лучше не мешать плюсовые объектники не просто от разных компиляторов, но даже от разных major версий одного компилятора.

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

А на моей памяти это прлисходит чуть ли не каждое обновление системы

Там где нужна стабильность ABI никто в здравом уме source-based дистрибутивы использовать не будет. Все наши upgrades между версиями RHEL проходили относительно безболезненно. Но у нас из OS-supplied runtime зависимости только на C-шный stuff, всё плюсовое (включая 3rd party libs) собирается «нашим» компилятором под самый старый RH в проде.

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

Тем не менее, тот же std::string и std::vector очень многие хотят в API, хотя бы ссылкой. если придерживаться правила «не держать STL в API», то всё ок, но тут другая проблема - у простого char* нельзя взять длину как O(1). Нужна абcтракция вроде {ptr,len} или {ptr,end}. Можно конечно понадеятсья что string_view ABI меньше будет ломать, но основний для этого нет и появился он совсем недавно. Слишком поздно даже - его надо было делать до и даже вместо std::string

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

Я бы сказал, что это скорее костыль чтобы исправить проблемы «стандарта», которому это слово никак не подходит. Уж доходит до ситуации, когда безопаснее чем «стандарт» использовать свои утилиты. Да что уж там говорить, у всех крупных c++ проектов как llvm, qt свои даже строки.
Ваш подход чем-то аналогичен ситуации, которую я привёл с rust как пример - там всё компилируется статически вместе с софтом одной версией компилятора чтобы уж точно ничего никуда не развалилось. Но это лишь говорит что такой «стандарт» не совсем стандарт т.к не смог стандартизовать стабильное abi.
Да, естати, ещё вспомнил, чем мне не нравился STL в текущем виде:
Пытается быть потокобезоапсным независимо нужно этого или нет. К сожалению этим запортили c++ даже без стдлибы, но с stl это особенно плохо.
В итоге tuple на сотню элементов, где в элементе был std::shared_ptr у меня генерировал деструктор огромного размера, на котором компилятор мог упасть с OOM, а сам вызов его длился несколько секунд т.к лочил какие-то мьютексы или спинлоки. Явная перегрузка деструктора на пустой ВНЕЗАПНО помогала. Замена shared_ptr на свою реализацию - тоже. И нужна ли такая токая стдлиба? А добавлять многопоточность шаблоном - так это опять же поломает ABI опять и ещё сильнее раздует длину функций.
Ну и второе - инлайновые методы всякого std:string и прочего зачастую ломали строки в дебаге.
если в функции, которая работает под отладчиком есть std::string - то отладчик может при брейкпоинте в теле функции указывать строки в хидерах, так же в эти строки попадать при степпинге, соотвественно не давая смотреть нормально локальный фрейм. Это не делает отладку совсем невозможной, но значительно её усложняет.

Нередко stl/boost конечно упрощает разработку, но ценой этого несоразмерное усложнение в некоторых случаях

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

Тем не менее, тот же std::string и std::vector очень многие хотят в API

std::string «ломали» только один раз при переходе от COW на SSO, и то в теории оно должно работать (не проверял) так как разные версии ABI по разным namespace’s разнесли. А в std::vector вообще ломаться нечему. Удивлён что Вы std::list не упомянули - там тоже слом был: примерно та же история что и со стригами - начали кешировать size и sizeof + memory layout съехал, но разные ABI разнесены по разным namespaces.

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

кстати, этот полимофрный вариант можно будет как есть без изменения данных копировать/перемещать?
Вариант с кучей требует всё же аллокаций в конструктре копирования, в то время, когда можно было обойтись быстрым memcpy. А реализация move-семантики в c++ довольно сложна в использовании - очень легко можно получить код менее эффективный чем с копированием, да и NRVO почти никогда толком не работает

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

там возможно memory layout и совсестимый, а вот сигнатура функции с std::string в аргументах может меняться, потому ошибки происходят именно на стадии линковки, либо же, что ещё веселее, динамической линковки. В подробности не вдавался, но если коротко - 2 объектника собранные разной версией стандарта или компилятора дают разные сигнатуры функций

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

кстати, этот полимофрный вариант можно будет как есть без изменения данных копировать/перемещать?

pmr версия - это, вроде как, частичная специализация шаблона полиморфным аллокатором. Все остальное работает «из коробки», просто вместо кучи буфер. Копирование копипастит данные (может даже в новый аллокатор), мув просто переприсывает указатель. Пока буфер цел, мув работает.

Вариант с кучей требует всё же аллокаций в конструктре копирования, в то время, когда можно было обойтись быстрым memcpy

Если конструктор есть, он должен быть вызван, memcpy тут не поможет. Максимум может быть вызвано в конструкторе.

А реализация move-семантики в c++ довольно сложна в использовании - очень легко можно получить код менее эффективный чем с копированием

Да ладно) Расставил двойной амперсенд где надо и мувнул где надо, делов то)). Единственное требование на класс - это наличие «пустого» состояния. Для классов со сложными инвариантами это может быть проблемой.

NRVO почти никогда толком не работает

Начиная с 17-х copy elision обязателен. В худшем случае мувнеться.

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

Да ладно) Расставил двойной амперсенд где надо и мувнул где надо, делов то)). Единственное требование на класс - это наличие «пустого» состояния. Для классов со сложными инвариантами это может быть проблемой.

Тут есть одна сложность: добавленный не туда мув вызовет лишнее копирование объекта например не дав сделать copy elision автоматичски. При этом заметить такую проблему без специальной отладки довольно сложно. И лёгким движением руки программа начинает жрать в разы больше cpu при активном копировании объектов. в общем, я пока предпочитаю избегать мува кроме случаев, когда этого требует какой-нибудь внешний ресурс типа дескриптора

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

Как данная проблема решается в managed языках?

Никак, там используются бокснутые/референсные типы. Т.е. место под строку выделяется где-то отдельно, а не в самой структуре.

no-such-file ★★★★★
()
Ответ на: комментарий от PRN

Потому что оно там давно есть

Это не то что хочет ТС. Тут длина задаётся статически, а он хочет чтобы она задавалась динамически. Компилятор видимо должен узнать эту длину на этапе компиляции из астрала.

no-such-file ★★★★★
()

Описание того что я хочу

Если я правильно понял, хочется размещать структуру с полями переменной длины в монолитном куске памяти, в рантайме, со всроенным в компилятор этой «монолитной стркутуры».

Технически, такое сделать конечно можно. Навскидку, если делать в компиляторе, я бы к структурам из примера добавил описательную часть содержимого монолитного куска памяти (типа таблицу где какое поле находится), обращения к полям заменил бы на вызов какой-нибудь встроенной в компилятор функции или ее инлайн вариант, которая вернет адрес поля в соответствии с описанием, а код, производимый компилятором, работал бы не с константными смещениями, а с полученным адресом от встроенной функции компилятора. Таблица с описанием поможет и перестраивать такую структуру в рантайме при изменении размера полей.

Но именно такой реализации, что бы был именно монолитный кусок памяти, я ни в одном языке не встречал.

То, что кто-нибудь когда-нибудь пробовал так сделать – это сто процентов. Но, видимо, не прижилось, предположу, что из-за итоговой скорости работы программы. Обращения к полям структур, смещение которых вычислено на стадии компиляции программы, выполняется непосредственно самими командами процессора, что на намного быстрее (возможно на порядки), чем делать вычисления адреса поля структуры в рантайме (поглядите на свою же конструкцию по вычислению размера поля [ i->description.length ], а ведь примерно так же придется вычислять адрес потом при каждом обращении к содержимому такого поля).

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

@MOPKOBKA, можно поинтересоваться, зачем вам монолитная в памяти структура и почему не подошли обычные C++ классы плюс сериализация?

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

Таблица которая запоминает адреса дальних полей, очень желательна, без нее будет совсем медленно, и желательно что бы в различных циклах по массиву таких структур, компилятор запоминал текущий адрес, и при arr[++i], не пересчитывал все arr[0...++i].

@MOPKOBKA, можно поинтересоваться, зачем вам монолитная в памяти структура и почему не подошли обычные C++ классы плюс сериализация?

1. Для сложных структур на диске, обычно пишут код с fread(), fseek(), это эмуляция структур, код никак не проверяется компилятором, много ручного вычисления полей, мое предложение проблемы не решит, но я считаю это могло бы быть первым шагом, надо с чего то развивать возможность удобно и проверяемо описывать работу с такими структурами.

2. В ассемблерном коде своей программы, я использую массив элементов, просто размышлял как переписать на это на С:

struct item {
  short name_len;
  char name[];
  short data_len;
  char data[];
};
Проблем с реализацией нету, но я потеряю преимущество С, иметь нормальные структуры, и не вычислять смещения самому. По массиву мне всегда нужно проходиться последовательно от 0 до (i < N).

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