LINUX.ORG.RU

статический flexible array

 


0

3

Хочу в контроллере выделить пару однотипных структур с массивами разного размера. Для одной это делается просто:

struct{
  int field;
  char arr[ SIZE ];
}var;

Если же делать для нескольких, уже нужен прототип

typedef struct{
  int field;
  char arr[];
}str_t;
Объявление может быть только глобальным или статическим (в моем случае это не проблема). Но способа красиво задать размер я не нашел. Разве что вот такой костыль:
str_t var = {.arr[ SIZE ]=0};
Нет ли более человеческого способа?

str_t var = {.arr[ SIZE ]=0};

Что ты хотел сделать такой конструкцией? Мне кажется оно не скомпилируется вообще, а если и скомпилируется - то означать будет совсем не то, что ты хотел.

Уже заданный тип (у тебя str_t) нельзя варьировать, а при статическом объявлении переменных они будут именно этого типа всегда. Кажется подобное можно сделать в C++ с помощью шаблонов.

А зачем это всё? Может есть способ лучше сделать то же самое.

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

Что ты хотел сделать такой конструкцией?

Объявить массив размером SIZE+1 байт путем явной инициализации SIZE'того. Судя по смещениямм последовательно объявленных переменных, это извращение все же работает.

А зачем это всё? Может есть способ лучше сделать то же самое.

Ну, собственно ради этого я тему и создал. Костыльный способ нашелся, но может есть нормальный?

COKPOWEHEU ()

Можно создать макрос-«шаблон», но имхо, будет ещё уродливее, чем текущий вариант.

mxfm ★★ ()

Компилятора C++ под целевую архитектуру нет? Или нет желания использовать плюсы?

u-235 ()

Вообще это так используется

struct s { int n; double d[]; }; // s.d is a flexible array member 
struct s *s1 = malloc(sizeof (struct s) + (sizeof (double) * 8)); // as if d was double d[8]

А твоё вообще вряд ли валидно.

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

А не нарушает ли твой способ стандарт? Даже если нет - проверь на compiler explorer, чтобы не пришлось всё переделывать потом при смене компилятора. Ну и чему равен sizeof структуры в этом случае?

mittorn ★★★★★ ()

Нет ли более человеческого способа?

Способов много.

У меня например разработана функция, которой передается путь текст в котором определены #include /любое количество и любой вложенности/.

Функция парсит все #include и создает метаданные о всех struct, …

Конечно имеется API, которое умеет использовать эти метаданные для создания объектов, …


Для вашего случая можно например поступить так.

Любой массив переменной длины должен содержать последним значением некоторую константу.
Это позволит легко получать размер массива …

Владимир

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

Автор указал, что

Объявление может быть только глобальным или статическим

т.е, случай с переменной внутри функции (нестатической) не рассматривается, поэтому malloc тут не нужен.

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

Я имел ввиду зачем тебе статические переменные именно такого вида?

Судя по смещениямм последовательно объявленных переменных, это извращение все же работает.

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

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

Сишные шаблоны:

Ну а теперь передайте это в функцию.

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

Макрос не сможет им сделать одинаковый тип. А тут как раз одинаковый получается - gcc выделяет память для строки но в спецификации типа её как бы нет (sizeof маленький).

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

А не нарушает ли твой способ стандарт? Даже если нет - проверь на compiler explorer, чтобы не пришлось всё переделывать потом при смене компилятора.

Компилятор нужен для того, чтобы помогать программисту, а не для того, чтобы портить ему поддержку фич. Не надо менять компилятор с хорошего на плохой.

Ну и чему равен sizeof структуры в этом случае?

Весьма элегантное решение оказалось - ничего не ломает но динамическое поле создаёт.

#include <stdio.h>

typedef struct {
  int a;
  char b[];
} str_t;

str_t A1 = { .b[4] = 0 };
str_t A2 = { .b[14] = 0 };
str_t A3 = { .b[104] = 0 };
str_t A4 = { .b[1004] = 0 };

int main(void) {
  printf("%u %u\n", (unsigned)&A1, sizeof(A1));
  printf("%u %u\n", (unsigned)&A2, sizeof(A2));
  printf("%u %u\n", (unsigned)&A3, sizeof(A3));
  printf("%u %u\n", (unsigned)&A4, sizeof(A4));
  return 0;
}
$ ./a.out 
4665376 4
4665388 4
4665408 4
4665520 4

Но что меня больше удивило так это то что оно с -std=c89 скомпилилось.

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

Просто gcc весьма пермиссивен. Вывода типов там нет. Размер типа не соответствует размеру твоих данных. Как поведут себя всякие санитайзеры и оптимизаторы не понятно

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

Просто gcc весьма пермиссивен.

Нет, это не пермиссивность, это специально заложенная фича. Её совершенно невозможно было бы сделать, просто скипнув какую-нить проверку. Удивление вызвано тем, что обычно такие штуки только в -std=gnuXX включаются а не в -std=cXX и тем что flexible arrays вообще (даже не статические) появились весьма позже чем c89.

Как поведут себя всякие санитайзеры и оптимизаторы не понятно

Всмысле «как поведут»? gcc сделал бинарник, инструментов которые что-то в нём после компиляции уже оптимизируют (кроме strip) я не видел, по крайней мере в хоть сколько-то широком использовании. А те, что в ходе компиляции - они часть gcc и разумеется они знают про эту штуку и никак ей не повредят.

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

Размер типа не соответствует размеру твоих данных.

Прямо как в x = malloc(sizeof(*x)+datalen); которое штатное для flexible arrays.

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

Оказалось clang такое тоже поддерживает, правда с чуть другим синтаксисом (gcc тоже его принимает):

typedef struct {
  int a;
  char b[];
} str_t;

str_t A1 = { .b = { [4] = 0 } };
}

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

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

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

В том и дело, что «как сейчас» - тип одинаковый. Он формально не покрывает тот flexible array, это просто байты в секции данных после переменной.

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

https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html

GCC allows static initialization of flexible array members. This is equivalent to defining a new structure containing the original structure followed by an array of sufficient size to contain the data.

Так что все нормально будет работать.

С расширениями GCC два минуса обычно:

  • если кто-то другой будет этот код читать, далеко не сразу поймет в чем дело (что видно даже по комментариям выше);
  • про всякие нюансы нужно постоянно помнить. Если весь код на расширениях, значит пора вместо си плюсы те же брать или писать проще.
zent ()

Вообще это так используется

malloc

В контроллере не хочется использовать динамическую память.

Ну и чему равен sizeof структуры в этом случае?

Размеру фиксированной части. Поскольку sizeof возвращает размер типа, а не переменной, «хвост» он не видит.

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

Это позволит легко получать размер массива …

А зачем? Размер массива я и так знаю, я же его руками указываю.

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

А зачем? Размер массива я и так знаю, я же его руками указываю.

Например для того, чтобы в struct не задавать явно размер массива.

Не мой велосипед …

anonymous ()

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

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

Например для того, чтобы в struct не задавать явно размер массива.

А чем выделение ячейки под размер в конце переменной секции лучше выделения ячейки в постоянной? Я только минусы вижу: можно перепутать с полезными данными, нет явного имени.

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

Весьма элегантное решение оказалось - ничего не ломает но динамическое поле создаёт.

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

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

Передавать структуру по значению вообще не самая лучшая идея...

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

А чем выделение ячейки под размер в конце переменной секции лучше выделения ячейки в постоянной? Я только минусы вижу: можно перепутать с полезными данными, нет явного имени.

Это да, иногда такой способ не пригоден.

В notepad++ такой способ иногда используется, например для массива с данными о поддерживаемых language …

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

Собственно, вот для чего это было нужно: https://youtu.be/QAGFZ8X6X8U

Три поля вывода текста, каждый со своим буфером. Разумеется, полей может быть и больше, лишь бы памяти хватило.

COKPOWEHEU ()

Я решил перепроверить, что написано в стандарте (отсюда - http://www.open-std.org/JTC1/SC22/WG14/www/docs/n2731.pdf это примерно версия С18, но в С99 наверное также).

По ссылке на странице 93 написано, что это UB:

struct s t2 = { 1, { 4.2 }}; // invalid

The initialization of t2 is invalid (and violates a constraint) because struct s is treated as if it did not contain member d.

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

В gcc нет преждупреждения с -Wall и -Wextra, но появляется если добавить pedantic:

warning: initialization of a flexible array member [-Wpedantic]

При этом вариант типа var = {.arr[ SIZE ]=«text»}; не компилируется. Если туда добавить вместо строки массив отдельных char, то будет предупреждение на втором элементе char, что элементов больше, чем размер массива - то есть gcc предполагает, что в таком случае память выделяется под один char, даже если SIZE равен 4 или 11. Судя по объектному файлу - действительно, место выделяется под один символ. Получается, что решение является нерабочим: можно указать хоть SIZE=1000, но память будет выделена только для одного элемента.

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

Но что меня больше удивило так это то что оно с -std=c89 скомпилилось.

Если добавить -pedantic, то

warning: ISO C90 does not support flexible array members

mxfm ★★ ()

Я бы не заморачивался с человеческим способом и поступил бы так: сначала объявил бы char arr[] фиксированного размера отдельно для каждой переменной, затем в начале работы программы создал код, который в каждой str_t в элемент arr подставляет указатель на нужный буфер. В таком случае память корректно выделяется, и имеется структура с flexible array member (если эта вещь так сильно нужна).

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

Оберни макроснёй с обширным комментарием

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

Да я и говорю что включение gcc фич на стандартах c а не gnu - и есть пермиссивность

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

По ссылке на странице 93 написано, что это UB

Что violates a constraint.

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

По ссылке на странице 93 написано, что это UB:

«invalid» это не UB, а «не скомпилируется»

При этом вариант типа var = {.arr[ SIZE ]=«text»}; не компилируется.

тут [SIZE] это не размер массива а индекс в нём, разумеется присвоить символу указатель нельзя (без тайпкаста)

то есть gcc предполагает, что в таком случае память выделяется под один char, даже если SIZE равен 4 или 11. Судя по объектному файлу - действительно, место выделяется под один символ. Получается, что решение является нерабочим: можно указать хоть SIZE=1000, но память будет выделена только для одного элемента.

Ничего не понятно. gcc для этой конструкции выделяет SIZE+1 байт, в послений из которых записывает то что после знака равенства (потому что это инициализация SIZE-ного элемента массива). Чтобы инициализировать весь массив, а не символ, надо писать .arr = «text» (будет 5 байт, указать явно нельзя).

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

«invalid» это не UB, а «не скомпилируется»

В стандарте написано, что само declaration is invalid, при этом чтение/запись в flexible array member (за исключением первого элемента при определённых случаях) - это UB («The assignment to t1.d[0] is probably undefined behavior, but it is possible that …»). Короче говоря, писать или читать из такого массива для практических целей - UB.

Ничего не понятно. gcc для этой конструкции выделяет SIZE+1 байт, в послений из которых записывает то что после знака равенства (потому что это инициализация SIZE-ного элемента массива).

Речь о том, что в конструкции var = {.arr[5]=0}; память выделяется только для одного char, а не для пяти (в независимости от того, что понималось под SIZE - индекс или размер массива). Даже если под SIZE понимался индекс массива (допустим 5), как создать массив с размером меньше, чем 5? В любом случае на моей машине gcc выделяет не SIZE+1 байт, а ровно 1 байт.

Чтобы инициализировать весь массив, а не символ, надо писать .arr = «text» (будет 5 байт, указать явно нельзя).

Инициализировать массив можно так: var = {.arr[5]= {‘A’, ‘B’, ‘C’} }, но судя по дампу gcc выделяет память только для A, для остальных жалуется на избыточность списка. Я не понимаю как у автора работал код из первого поста.

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

Возможно чего-то не понимаю, но в чём проблема просто создать массив нужного размера(или alloca звать, если доступна), а для структур просто указатели повесить по нужному смещению ? При желании можете это всё дело в самопальную аlloca и обернуть. Или вопрос больше эстетический ?

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

В стандарте написано, что само declaration is invalid, при этом чтение/запись в flexible array member (за исключением первого элемента при определённых случаях) - это UB («The assignment to t1.d[0] is probably undefined behavior, but it is possible that …»). Короче говоря, писать или читать из такого массива для практических целей - UB.

Ты немного упустил суть темы. Если flexible array указан в статически выделенной переменной и у этой переменной нет инициализаторов для этого flexible array - то это что в том документе, что в реальном gcc последующее присваивание чего-то любым (и первому тоже) элементам этого массива будет выходом за границу массива (потому что у flexible array нет собственного размера, собственно сам flexible array тут ни причём вообще). Другая ситуация - если в инициализаторе указаны байты для этого flexible array, тут уже расхождение: твой документ называет это невалидным кодом, а gcc считает что это переменная с статическим массивом ненулевой длины. И никакого UB тут уже не будет.

Речь о том, что в конструкции var = {.arr[5]=0}; память выделяется только для одного char, а не для пяти (в независимости от того, что понималось под SIZE - индекс или размер массива). Даже если под SIZE понимался индекс массива (допустим 5), как создать массив с размером меньше, чем 5? В любом случае на моей машине gcc выделяет не SIZE+1 байт, а ровно 1 байт.

gcc тут выделит не 1 и не 5 байт, а 6. Потому что для записи чего-то в arr[5] должны существовать байты с [0] по [5] - всего 6. Повторю, в квадратных скобках не размер а индекс. Статических массивов, где есть пятый элемент, но нет нулевого, синтаксис не предусматривает. Я выше приводил проверочный код - скомпиль его как есть и покажи результат.

Инициализировать массив можно так: var = {.arr[5]= {‘A’, ‘B’, ‘C’} }, но судя по дампу gcc выделяет память только для A, для остальных жалуется на избыточность списка. Я не понимаю как у автора работал код из первого поста.

Нельзя. Ты пытаешься пятому элементу массива присвоить массив. И автор такого не писал. Повторяю ещё раз, arr[5] тут это не массив из пяти элементов, это пятый элемент массива arr. Единственное, где такая запись означает массив из 5 элементов - это объявление переменной (не инициализация!).

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

Указатель на выделенный где-то массив и сам массив - разные вещи. Хотя на практике часто можно одно заменить другим, но: 1) массив + указатель занимают на 4(8) байт больше памяти чем просто массив 2) другая бинарная структура структуры 3) это действие (выделение массива на стороне) придётся явным образом прописывать везде где инициализируется структура.

Да и какое alloca, тут речь про глобальные переменные.

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

Вот более удобный вариант проверочного кода

#include <stdio.h>

typedef struct {
  int a;
  char b[];
} str_t;

str_t A1 = { .b[4] = 0 };
str_t A2 = { .b[14] = 0 };
str_t A3 = { .b[104] = 0 };
str_t A4 = { .b[1004] = 0 };

int main(void) {
  printf("A1 sizeof=%u realsize=%u\n", (unsigned)sizeof(A1), (unsigned)&A2 - (unsigned)&A1);
  printf("A2 sizeof=%u realsize=%u\n", (unsigned)sizeof(A2), (unsigned)&A3 - (unsigned)&A2);
  printf("A3 sizeof=%u realsize=%u\n", (unsigned)sizeof(A3), (unsigned)&A4 - (unsigned)&A3);
  printf("A4 sizeof=%u\n", (unsigned)sizeof(A4));
  return 0;
}

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

Да, я неправильно тестировал код. Вместе с тем этот код не совсем по стандарту. С -pedantic сохраняется warning на flexible array member initialization.

Clang подобный код не компилирует с ошибкой:

error: designator into flexible array member subobject

Синтаксис .b = {‘A’, ‘B’, ‘C’} работает, но с предупреждением:

warning: flexible array initialization is a GNU extension [-Wgnu-flexible-array-initializer]

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

Вместе с тем этот код не совсем по стандарту.

Так я и писал - в твоём документе заявляется что он не скомпилируется.

Но реальный компилятор его поддерживает, а значит всё хорошо. Разработчики альтернативного компилятора (clang) тоже решили не умничать (у них случалось) и реализовали поддержку как положено.

firkax ★★★ ()
Для того чтобы оставить комментарий войдите или зарегистрируйтесь.