LINUX.ORG.RU

_Generic форматирование строки в С

 , , ,


1

3

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

Есть два вопроса.

Есть такой макрос

#define PFMT(x)                         \
  _Generic( (x),                        \
    int               : "%d",           \
    long              : "%ld",          \
    double            : "%f",           \
    float             : "%f",           \
    struct POINT2     : "(%6.3f, %6.3f)"\
)

// универсальный printf
#define PRINT(x)    printf( PFMT( (x) ), (x) )

Этот PRINT работает отлично.

А как сделать, чтобы выводилось с доп. строковой информацией, вроде такого:

printf("Значение "##PFMT(k)##" вне заданного диапазона\n", k);

Это не компилируется (с символами конкатенации ##).

Нужно чтобы выводилось, например, для

<type> k=2025;

строка

Значение 2025 вне заданного диапазона\n

без зависимости от типа type (согласно вышеописанному макросу с Generic )

  1. Как средствами макропроцессора вывести с дополнит. словами, а не только само значение?

Второй вопрос.

Если есть структура

typedef struct POINT2
{
   <real_type> X,Y;
} POINT2;

где real_type м.б. или float, или double.

И например задано

POINT2 pt = (POINT2){7.0f, 13.0f};
PRINT(pt);

то корректно выводится значение точки только в случае real_type=double.

Если real_type=float, то

(4194305.014, -5487458222577050490711218714016849409047503760087531831195489080004217105968837296341131586189673352421011955033155197938514935435243923001276435782347988725676512038006187992514731981983482317923338165440313421583742788594446834364183181944583960924683897740668190287231179824590846527476002567405699072.000)

и компилируется с warning-ами

format ‘%f’ expects argument of type ‘double’, but argument 2 has type ‘POINT2’ {aka ‘struct POINT2’} [-Wformat=]
  246 |     struct POINT2     : "(%6.3f, %6.3f)",  \```

Это из-за того что для форматирования float и double исп. один и тот же символ форматирования %f.

  1. Как сделать чтобы все таки различался вывод float и double, и для float стало выводить корректно?

Заранее благодарен за содержательный ответы по существу. Спасибо!



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

то корректно выводится значение точки только в случае real_type=double.

Я удивлён что он с double корректно работает. Тебе просто повезло, не надо на такое рассчитывать. Сделай ещё один generic-макрос, который будет превращать POINT2 в два его поля, идущие через запятую (а остальные типы пробрасывать как есть). Только учти, что если POINT2, данный ему на вход, вычисляемый, будет считаться два раза. Чтобы это исправить, сделай generic-макрос для самой печати, где для POINT2 полностью отдельная ветка: сложить структуру во временную переменную, а потом печатать поля из неё.

Как сделать чтобы все таки различался вывод float и double, и для float стало выводить корректно?

printf не умеет печатать float-ы вообще. Когда ты ему суёшь float в аргумент, компилятор автоматически конвертирует его (это касается всех функций с переменным числом аргументов). А вот структуру он не конвертирует и получается ерунда.

Та же самая история с типами char/short/int. Первые два на самом деле не поддерживаются, компилятор их конвертирует в int перед тем как отправить в printf.

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

Либо делаешь два генерик принта, один для базовых типов, второй для составных имеющих одинаковое количество полей. Либо пишешь как тут ниже «проксирующие» функции. В printf не сувай =) просто структуру по значению, надо совать поля структуры, раскрывать структуру и форматировать вывод значений полей составного типа.

Так что либо вот так, всё в одном

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

struct DPOINT2
{
   double X,Y;
} DPOINT2;

struct FPOINT2
{
   float X,Y;
} FPOINT2;

#define MSG1 "Значение"
#define MSG2 "вне заданного диапазона\n"

void p_int(int x)
{
    printf( MSG1 " i %d " MSG2, x);
}

void p_long(long x)
{
    printf( MSG1 " l %ld " MSG2, x);
}

void p_double(double x)
{
    printf( MSG1 " d %f  " MSG2, x);
}

void p_float(float x)
{
    printf( MSG1 " f %f  " MSG2, x);
}

void p_fpoint(struct FPOINT2 x)
{
    printf( MSG1 " d(%6.3f, %6.3f) " MSG2 ,x.X,x.Y);
}

void p_dpoint(struct DPOINT2 x)
{
    printf( MSG1 " f(%6.3f, %6.3f) " MSG2 ,x.X,x.Y);
}

#define PRINT(x)                   \
  _Generic( (x),                    \
    int               : p_int,      \
    long              : p_long,     \
    double            : p_double,   \
    float             : p_float,    \
    struct DPOINT2    : p_dpoint,   \
    struct FPOINT2    : p_fpoint )(x)



int main(int argc, char *argv[])
{

    struct DPOINT2 dp = {.X=42.0, .Y=0.42};
    struct FPOINT2 fp = {.X=42.0, .Y=0.42};

    PRINT(dp);
    PRINT(((struct DPOINT2){42.0,0.42}));

    PRINT(fp);
    PRINT(((struct FPOINT2){42.0,0.42}));

    PRINT(1);
    PRINT(1L);
    PRINT(1.0f);
    PRINT(1.0);

    return 0;
}

Либо вот так, раздельно

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

struct DPOINT2
{
   double X,Y;
} DPOINT2;

struct FPOINT2
{
   float X,Y;
} FPOINT2;


#define MSG1 "Значение"
#define MSG2 "вне заданного диапазона\n"

#define PRINT2(x)                                                       \
  _Generic( (x),                                                        \
    struct DPOINT2    : printf( MSG1 " d(%6.3f, %6.3f) " MSG2 ,x.X,x.Y),\
    struct FPOINT2    : printf( MSG1 " f(%6.3f, %6.3f) " MSG2 ,x.X,x.Y) )

#define PRINT1(x)                                       \
  _Generic( (x),                                        \
    int               : printf( MSG1 " i %d  " MSG2, x),\
    long              : printf( MSG1 " l %ld " MSG2, x),\
    double            : printf( MSG1 " d %f  " MSG2, x),\
    float             : printf( MSG1 " f %f  " MSG2, x) )


int main(int argc, char *argv[])
{

    struct DPOINT2 dp = {.X=42.0, .Y=0.42};
    struct FPOINT2 fp = {.X=42.0, .Y=0.42};

    PRINT2(dp);
    PRINT2(((struct DPOINT2){42.0,0.42}));


    PRINT2(fp);
    PRINT2(((struct FPOINT2){42.0,0.42}));

    PRINT1(1);
    PRINT1(1L);
    PRINT1(1.0f);
    PRINT1(1.0);

    return 0;
}

Ты хочешь просто вот так

#define PRINT1(x)                                       \
  _Generic( (x),                                        \
    int               : printf( MSG1 " i %d  " MSG2, x),\
    long              : printf( MSG1 " l %ld " MSG2, x),\
    double            : printf( MSG1 " d %f  " MSG2, x),\
    float             : printf( MSG1 " f %f  " MSG2, x),\
    struct DPOINT2    : printf( MSG1 " d(%6.3f, %6.3f) " MSG2 ,x.X,x.Y),\
    struct FPOINT2    : printf( MSG1 " f(%6.3f, %6.3f) " MSG2 ,x.X,x.Y) )

Вроде логично смотрится и красиво, только вот работать не будет для базовых типов int и так далее, все попрытки это обойти приведут к починил тут, сломал там и так и будут качели, все поля в одни скобочки, псевдополя для базовых типов, вместо полей передача по значению самой структуры и так далее, но не надо. Это как приводить 42.0 из float к массиву char[4] а потом к uint32_t и ожидать увидеть 42 в значении инта =))) Си это пускает спокойно так как предполагается что ты делаешь с данными то что тебе нужно, и если тебе нужно получить херню непонятную, будет исполнено и ты её получишь =)

Я сам постоянно косячу в этом плане :(
Если я тут где-то ошибаюсь буду рад если поправите :).

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)

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

Вот, посмотри как сделан в ядре printk:

https://docs.kernel.org/core-api/printk-formats.html#printk-specifiers

Он повторяет обычный printf по форматированию, но вводит несколько своих элементов для распечатки, например:

https://docs.kernel.org/core-api/printk-formats.html#device-tree-nodes

Предлагаю повторить работающий дизайн, если тебе просто нужно выводить логи. Если ты занимаешься отладкой через printf, то советую изучить gdb, им можно не просто отлаживать, его можно скриптовать, и писать pretty-printerы для своих структур. Сможешь распечатывать из него красиво все что тебе нужно увидеть, в любом формате каком захочешь.

Примеры с gdb для С++ классов, был страшный класс, а вывел как простую строку: https://sourceware.org/gdb/current/onlinedocs/gdb.html/Pretty_002dPrinter-Exa...

Если ты хочешь продолжить мучение, то LINUX-ORG-RU написал хороший ответ. По второму вопросу firkax тоже верно ответил, посмотри на правила преобразования для аргументов определяемых через три точки.

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

printf(«Значение »##PFMT(k)##" вне заданного диапазона\n", k);

Можно еще использовать системные, привычные и переведенные фразы.

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

int main(int argc, char **argv)
{
  setlocale(LC_ALL, "");
  printf("%s: %d\n", strerror(ERANGE), argc);
  return 0;
}
$ LC_ALL=ru_RU.UTF-8 ./a.out
Числовой результат вне представимого диапазона: 1

$ LC_ALL=en_US.UTF-8 ./a.out
Numerical result out of range: 1
https://learn.microsoft.com/ru-ru/cpp/c-runtime-library/errno-constants?view=...

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

Си слепляет строковые литералы идущие подряд.

int k=7;
printf("AAAAAAAAAAAA "PFMT(k)" BBBBBBBBBBB", k);

Результат:

In function ‘main’:
dynarr_use.c:35:27: error: expected ‘)’ before ‘_Generic’
   35 |     printf("AAAAAAAAAAAA "PFMT(k)" BBBBBBBBBBB", k);
      |                           ^
      |                           )
Gyros
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

Спасибо! Да я сделал уже. Я знал об этом (про проксирующие ф-ции).

#define FPRINT(output, x)\
  _Generic( (x),\
     unsigned char       : fprintf_byte,  \
     int                 : fprintf_int,   \
     long                : fprintf_long,  \
     float               : fprintf_float, \
     double              : fprintf_double,\
    struct POINT2        : fprintf_pt2,   \
    struct POINT3        : fprintf_pt3,   \
    struct TRIANGLE_INDEX: fprintf_index  \
)(output, x)

И когда надо выводить с доп. сообщением приходится так писать

fprintf(stdout, "Значение ");
FPRINT (stdout, k);
fprintf(stdout," вне заданного диапазона\n");

Менее красиво (но РАБОТАЕТ), чем хотелось бы гипотетически

// это не работает!
printf("Значение "PFMT(k)" вне заданного диапазона\n", k);
Gyros
() автор топика
Ответ на: комментарий от firkax

Спасибо! Но как-то сложновато звучит.

Думаю проще (!?) свой my_printf написать с собств. форматными символами (для POINT2 и т.п.),

как советует MOPKOBKA

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

Спасибо! Да я понял.

Придется видимо лезть в дебри.

Или не лезть, оставить как есть и пользоваться вариантом с проксирующими ф-циями.

Может приведу свой вариант, если получится.

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

Я еще не см. как printk устроен внутри.

Но модифицировал пример my_printf из интернета, ввел форматир. символ %q для точки.

Вот вариант, который поборол проблему с выводом POINT2<float>:

#include <stdarg.h>
#include <stdio.h>


typedef struct POINT2
{
    float X,Y;
} POINT2;

typedef struct dPOINT2
{
    double X,Y;
} dPOINT2;

#define TO_DBL(pt) (dPOINT2){.X = (double) pt.X, .Y = (double) pt.Y}


int my_printf(const char *format, ...)
{
    va_list args;
    va_start(args, format);

    size_t i = 0;
    while (format[i])
    {
        if (format[i] == '%')
        {
            i++;
            switch (format[i])
            {
                case 'd':
                {
                    int x = va_arg(args, int);
                    printf("%d", x);
                    break;
                }
                case 'f':
                {
                    double x = va_arg(args, double);
                    printf("%f", x);
                    break;
                }
                case 'c':
                {
                    int x = va_arg(args, int);
                    printf("%c", x);
                    break;
                }
                case 's':
                {
                    char *x = va_arg(args, char*);
                    printf("%s", x);
                    break;
                }
                case 'x':
                case 'X':
                {
                    int x = va_arg(args, int);
                    printf("%x", x);
                    break;
                }
                case 'p':
                {
                    void *x = va_arg(args, void*);
                    printf("%p", x);
                    break;
                }
                case 'q':
                {
                    double x = va_arg(args, double);
                    double y = va_arg(args, double);
                    printf("(%3.3f, %3.3f)", x, y);
                    break;
                }
                case '%':
                    putchar('%');
                    break;

                case '\0':
                    i--;
                    break;

                default:
                    putchar(format[i]);
                    break;
            }
        }
        else
        {
            putchar(format[i]);
        }
        i++;
    }

    va_end(args);
    return 0;
}



#define PFMT(x)                         \
  _Generic( (x),                        \
    char              : "%c",           \
    int               : "%d",           \
    long              : "%ld",          \
    double            : "%f",           \
    float             : "%f",           \
    char*             : "%s",           \
    struct  POINT2    : "%q"            \
)


static void printf_pt2(const char* fmt, const POINT2 pt)
{
    my_printf(fmt, TO_DBL(pt));
}



#define UNIVERS_PRINT(x)\
  _Generic( (x),\
    struct POINT2 : printf_pt2,  \
    default       : my_printf    \
)(PFMT(x), (x))





int main(void)
{
    int x = 5;
    double y = 3.14;
    char c = 'a';
    char *str = "Hello World!";
    POINT2 pt = (POINT2){4.444, 7.777};

    my_printf("The value of x is %d,\n y is %f,\n c is %c,\n str is %s,\n point is %q\n",
              x, y, c, str, TO_DBL(pt) );


    UNIVERS_PRINT(x);   putchar('\n');
    UNIVERS_PRINT(y);   putchar('\n');
    UNIVERS_PRINT(c);   putchar('\n');
    UNIVERS_PRINT(str); putchar('\n');
    UNIVERS_PRINT(pt);  putchar('\n');
    return 0;
}

Результат:

The value of x is 5,
 y is 3.140000,
 c is a,
 str is Hello World!,
 point is (4.444, 7.777)
5
3.140000
a
Hello World!
(4.444, 7.777)
[Finished in 60ms]

Все компилируется без warning-ов.

Как вам такой my_printf?

Все передавать обычному printf-фу, а кастомизировать только для своих используемых типов и структур.

Т.е. делать универсальный и сложный не нужно.

А printk наверное сложный. (Извините, по ссылке его (тела ф-ции) не было, а я его еще не искал в интернете)

По идее my_printf должен возвр кол-во символов посланных в вых. поток. (А надо ли?)

Вообще этих my_printf-ов много в интернете, глаза разбегаются.

Выбрал попроще.

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

Да, почитал, va_arg не принимает float.

Я сделал вариант корректно выводящий POINT2<float>, с ухищрениями.

см. в ответе МОРКОВКЕ

Но не так как вы советовали, я не понял как

превращать POINT2 в два его поля, идущие через запятую

и далее

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

Но не так как вы советовали, я не понял как превращать POINT2 в два его поля, идущие через запятую

А да, я забыл что _Generic это не препроцессор и вернуть список с запятыми в середине он не может. Тогда да, только вспомогательными функциями для печати.

Только

   double x = va_arg(args, double);
   double y = va_arg(args, double);

Я тебе уже писал - не надо так делать, тебе повезло что оно случайно работает. В аргумент присылают структуру - значит забирай структуру (точно ту же самую) и никак иначе. va_arg(args, struct dPOINT2) заодно и конвертацию float можно будет убрать, потому что структуру из двух float-ов, в отличие от самих float-ов, va_arg принять вполне может.

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

Все ок, только нужно заменить это

#define TO_DBL(pt) (dPOINT2){.X = (double) pt.X, .Y = (double) pt.Y}
на вот это
#define TO_DBL(pt) (double) pt.X, (double) pt.Y
А printk действительно сложный, незачем его повторять, просто в ядре есть трудности с использованием libc printf %)

Только такой my_printf нельзя на файлах использовать, лучше сначала сделать свой vfprintf, а потом поверх него fprintf и printf.

Дополнение: Еще в glibc есть register_printf_function позволяющий расширить printf без написания своего, но это не стандартизировано.

По идее my_printf должен возвр кол-во символов посланных в вых. поток. (А надо ли?)

Так то надо проверять на ошибки, но никто этого не делает, поэтому можно хоть void оставить, если сам не планируешь. Но для fprintf это уже более важно.

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

Исправил как сказал firkax в my_printf

         ...
                case 'q':
                {
                    POINT2 p = va_arg(args, POINT2);
                    printf("(%3.3f, %3.3f)", p.X, p.Y);
                    break;
                }
         ...

Теперь просто

/*
#define UNIVERS_PRINT(x)\
  _Generic( (x),\
    struct POINT2 : printf_pt2,  \
    default       : my_printf    \
)(PFMT(x), (x))
*/

#define UNIVERS_PRINT(x) my_printf(PFMT(x), (x))

и TO_DBL и printf_pt2 не нужны.

Резюмирую.

Есть два способа универсального вывода (одним PRINT(x) для x разных типов) в C:

  1. через проксирующие ф-ции, как говорил LINUX-ORG-RU;

  2. через свой самописный my_printf с новыми символами форматирования для своих структур.

Других способов это делать в C нет.

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

Если формат вывода структуры не важен, то можно еще выводить все структуры через расширение __builtin_dump_struct.

Еще можно сделать макрос print с переменным количеством аргументов, который переводит свои аргументы вида (10, «123», point) в функцию my_pair_print(«%d», 10, «%s», «123», «%q», point). Но это будет выглядеть довольно уродливо в реализации.

Мне идея универсального вывода кажется странной, даже числа хочется вывести в разных форматах часто, с нулями, или наоборот без, в другой системе счисления итд.

Если все что нужно для вывода структуры, это добавить «%q», то зачем нужен еще один дополнительный слой?

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

У меня есть generic-контейнеры: динамич. массив, список, бин. дерево.

Они хранят не void* data, а TYPE item или TYPE data[] или TYPE *array

Это косплей шаблонов C++.

У них есть ф-ция container_print(FILE* out, CONTAINER *c).

Вот она и вызывает универс. вывод.

Это находится внутри файла с реализ. контейнера. Один раз сделал и забыл. И не лазиешь туда.

По умолчанию контейнер должен иметь такую ф-цию и, след-но, должен быть какой-то формат вывода (не универсальный, а конкретный, например %6.3f для членов float).

Если нужно вывести как-то по другому, то исп. container_get(CONTAINER* c, const size_t index) для доступа в элементам контейнера.

Реализован универс. вывод через проксирующие ф-ции.

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

static void fprintf_NEW_TYPE(FILE *output, const NEW_TYPE nt)
{
    fprintf(output,  " конкретный формат ", что-то конкретное);
}

и вставить в

#define FPRINT(output, x)\
  _Generic( (x),\
     unsigned char       : fprintf_byte,  \
     int                 : fprintf_int,   \
     long                : fprintf_long,  \
     float               : fprintf_float, \
     double              : fprintf_double,\
    struct POINT2        : fprintf_pt2,   \
    struct NEW_TYPE      : fprintf_NEW_TYPE\
)(output, x)

Так вот, я попробовал выводить не через прокс. ф-ции, а используя макрос PFMT. Корректно не работало (с float).

Вот я и замутил тему. Теперь разобрался и этот вариант останется теоретическим,. А исп. буду как раньше прокси-ф-ции.

Вот для чего все это.

А вы как свои (или не свои) контейнеры организовываете?

PS Не совсем понял про какой слой

то зачем нужен еще один дополнительный слой?

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

А вы как свои (или не свои) контейнеры организовываете?

Массив как структура включающая поля:

struct array {
  void *data;

  // size это то что обычно понимается под размером массива, 
  // если в массиве 5 элементов, то и size будет равен 5
  //
  // capacity это размер выделенной памяти, в пустом массиве 
  // это 0, в массиве до с 1..16 элементами capacity будет 16,
  // в массиве с 24 элементами 32, и далее так она и растет 
  // 0, 16, 32, 64, 128, 256, 512, ... Такой подход позволяет
  // не вызывать каждый раз realloc при добавлении элемента, а
  // только когда size достигает capacity, тогда capacity 
  // увеличивается в два раза, и можно черпать новые элементы
  //
  // Полезно заранее выставить capacity к примеру в 1000 
  // элементов, что бы обойтись в среднем парой вызовов realloc
  // на всю жизнь массива
  size_t capacity 
  size_t size;

  // Можно постоянно передавать размер элемента через аргументы
  // но мне это не нравится
  size_t element_size;
};

Еще часто использую pool элементов:

// Этот union не будет работать, но суть в том что
// освобожденные элементы образуют список из которого можно
// брать элементы для выделения. Вместо union просто type cast. 
union slot {
  uint32_t prev_free;
  char element[];
};

// Пул строится на массиве, а значит он так же поддерживает
// capacity который можно заранее установить
//
// Еще одно его преимущество для выделения множества объектов,
// это то, что пул можно убить одним free, вместе со всеми его 
// элементами, а не проходится по каждому элементу с free если
// каждый отдельный выделен через обычный malloc
struct pool {
  union slot *memory;
  size_t capacity;
  size_t size;
  size_t element_size;
  
  // При realloc указатели сбиваются, поэтому хорошо иметь
  // флаг запрещающий делать realloc после инициализации с 
  // определенным размером
  bool lock;
};

// Из пула можно выделять элементы, быстрее чем через malloc и
// уплотненно, так как нету никаких заголовков или 
// дополнительного выравнивания
void *pool_alloc(struct pool *self);

// Можно освобождать элементы, и при выделении новых (alloc)
// ячейки освобожденных элементов будут переиспользованы
void pool_free(struct pool *self, void *p);

// Можно брать индексы элементов в пуле, и хранить не указатели
// на элементы, а их индексы. Использование индексов вместо
// указателей, позволяет сократить использование памяти если
// указателей было много, uint32_t - 4 byte, pointer - 8 byte, 
// и в отличие от указателей, при realloc данных пула,  индексы
// будут указывать все на тот же объект
uint32_t pool_id(struct pool *self, void *p);

// Получение элемента по его индексу
void *pool_at(struct pool *self, uint32_t id);

Вместо привычных списков, использую интрузивные списки

struct list {
  struct list *prev;
  struct list *next;
  // Все, никаких данных он сам по себе не хранит!
};

// Функции для работы с ними довольно очевидные
...
void list_append(struct list *parent, struct list *child);
...

// А теперь к использованию, выдуманная структура игрока для
// примера
struct player {
  char name[128];
  uint32_t score;

  // Встраивается список таким способом 
  struct list lst;
};

// Теперь можно создать игроков A, B, C, и сделать из них
// список
struct list *players;

players = &A.lst;
list_append(players, &B.lst);
list_append(players, &C.lst);

// Имея указатель на список, и зная структуру на которой он 
// закреплен, легко получить указатель на структуру, достаточно
// вычесть адрес поля списка относительно самой структуры, для 
// этого обычно делается макрос
//
// struct player {
//   char name[128];  <-- 128 byte
//   uint32_t score;  <-- 4 byte
//   struct list lst; <-- 132
//                        player = (list_ptr - 132)
// };

У них есть ф-ция container_print(FILE* out, CONTAINER *c).

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

PS Не совсем понял про какой слой

Я подумал все это делается ради того что бы не набирать дополнительно «%q\n», и мне это показалось неоправданным ради экономии пары символов, но теперь я вижу что цель была другая, обобщенное программирование, тут сразу вспоминаются такие проекты:

https://rurban.github.io/ctl/ - тоже попытка повторить шаблоны

https://libcello.org/ - попытка сделать С более высокоуровневым

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

Массив у меня также устроен.

Тоже удваиваю емкость при достижении предельной (кот. задаю при создании массива, т.е. это м.б. не только степени двойки).

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

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

А интрузивные списки как сортируют? Ведь узлы не хранят данные? Или это уже будет как бы внешняя функция (не принадл. самому списку), а зависящая от той структуры, которая обволакивает list (в вашем случае это player)?

Пула нет ): Не дошли руки еще. Сейчас вожусь с бин. деревом поиска с вставкой в корень. Хочу чтобы часто используемые item-ы были ближе к корню.

Большое спасибо за информацию и ссылки. Интересные библиотеки. Есть что поизучать, надолго хватит.

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

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

Иногда еще хранят имя в виде const char *, тогда можно например при вызове функции очистки напоследок распечатать имя и статистику.

Еще полезно иметь функцию reset/clear, которая не удаляет контейнер, но делает его очищенным для наблюдателя. В твоем массиве такая функция бы просто сбрасывала size в 0, не трогая capacity. Если в коде есть цикл с удалением/созданием контейнера, то можно этого избежать.

А интрузивные списки как сортируют? Ведь узлы не хранят данные?

По узлу же можно получить структуру и данные. Для общей сортировки можно завести:

struct list *list_sort(
  struct list *input,

  // Или передавать просто offset
  // Функция для извлечения поля из списка, например 
  // извлечение score из player по списку 
  void *(*extractor)(struct list *), 

  // Стандартная функция сравнения
  int (*comparator)(void *, void *)
);

Дополнительные преимущества списков в таком стиле:

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

2. При этом, одно значение может содержаться сразу в нескольких списках, в player можно хоть десять штук struct list добавить

3. Легко получить часть списка зная значение, это просто поле

4. Идеально сочетаются с моим пулом %)

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

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

MOPKOBKA ★★★★★
()