LINUX.ORG.RU

Призываются мастера препроцессора С

 ,


0

2

У нас есть код типа

#define FUNC_BEGIN(name) \
    static some_struct_t s_##name; \
    static void init_##name() { \
        s_##name.init_func=init_##name; \
        int index=0;
#define FUNC_END() }
#define ADD_INT32(name) \
    s_##name.list[index].type=type_int32; \
    s_##name.list[index].ptr=&name;
#define ADD_STR(name) \
    s_##name.list[index].type=type_str; \
    s_##name.list[index].ptr=name;
// тут дальше много разных типов

static int32_t var1;
static char var2[100];

FUNC_BEGIN(f1)
    ADD_INT32(var1);
    ADD_STR(var2);
FUNC_END
// И таких функций в программе штук 300.
Тут много отрезано.. Что нужно? Нужно сделать функцию которая будет занулять все переменные указанные такими макросами. Сейчас в структуре есть массив, в который заносятся указатели и типы переменных. Чем не нравится - массив фиксированного размера, в каждом воркере создается свой экземпляр. Есть идея переместить его в статические пременные, в которых есть указатель на предидущую переменную. Чтобы получился код типа такого:
typedef struct {
  void* ptr;
  int16_t type;
  const var_rec_t* prev;
} var_rec_t;
static var_rec_t init_var1={&var1, type_int32, NULL};
static var_rec_t init_var2={var2, type_str, &init_var1};
Вот как бы такое сделать макросами? Проблему у меня вызывает указатель на предидущую перменную.

Напиши лучше, что тебе нужно и зачем

Gvidon ★★★★ ()
#define ADD_INT32(name) \
    s_##name.list[index].type=type_int32; \
    s_##name.list[index].ptr=&name;

Тут где-то пропущен инкримент index"а?

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

Занулять - это пройтись по *s_##name.list[0 ... N].ptr = 0? В чем сложность?

Чем не нравится - массив фиксированного размера, в каждом воркере создается свой экземпляр.

Это наоборот хорошо, что в каждом воркере свой экземпляр. Чем плох статический размер?

Есть идея переместить его в статические пременные, в которых есть указатель на предидущую переменную. Чтобы получился код типа такого:

Линкедлист, только вот зачем?

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

Ну впили ещё один статик, в который записывай последний init_var* и инкрементируй.

Какой у тебя хоть конпелятор, какой стандарт?

anonymous ()
Ответ на: комментарий от yoghurt
static int32_t _id;
static char _value[256 + 1];

STMT_PREPARE(stmt_test_get_value, tt_test,
        "SELECT FIRST 1 col_tt_varchar256 FROM test_table WHERE id=:rec_id",
        "SELECT col_tt_varchar256 FROM test_table WHERE id=$1 LIMIT 1"
        );
TT_BIND_PARAMETER_INT32(_id);
TT_BIND_COL_VARCHAR(_value);

TT_PREPARE_DONE

void Statement::SelectNullString() {
    CPPUNIT_ASSERT(RETCODE_OK == db_test_connect());
    CPPUNIT_ASSERT(RETCODE_OK == tt_execute_direct(&tt_test, "INSERT INTO test_table (id, col_tt_varchar256) VALUES (1, 'test')"));
    CPPUNIT_ASSERT(RETCODE_OK == tt_execute_direct(&tt_test, "INSERT INTO test_table (id) VALUES (2)"));
    _id = 1;
    CPPUNIT_ASSERT(tt_execute_stmt_fetch(&stmt_test_get_value));
    CPPUNIT_ASSERT(0 == strcmp(_value, "test"));
    _id = 2;
    CPPUNIT_ASSERT(tt_execute_stmt_fetch(&stmt_test_get_value));
    CPPUNIT_ASSERT(0 == strcmp(_value, ""));
}

Вот такой у нас код.. Это обертка поверх ODBC. Дело в том, что есть некоторые драйвера ODBC, которые не зануяют переменную, если связананая с ней колонка NULL. Для такого драфвера последнее условие в тесте не пройдет. Можно принудительно занулять переменные перед каждым fetch, но это не удобно, если код который вызывает fetch не знает о том, какие перменные надо занулить.

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

не всегда известно что нужно занулять в том месте, где это надо делать. Т.е. некоторая функция в которую передается some_struct_t*. Надо пройти по списку из структуры и занулить переменные. Также если переменная типа char[10000], то memset будет куда медленее чем просто зануление первого символа.

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

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

vromanov ★★ ()

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

если правильно понял, то хотите что-то типа:

static var_rec_t head_of_list=NULL;
// работает только в С++, да и то не везде 
#define START_DECL head_of_list=NULL
#define DECLARE_REC(name,data,type) var_rec_t name = {data,ptr,head_of_list} ; head_of_list=&name
#define ANONYM_REC(data,type) DECLARE_REC("the_anonymous_rec"##__LINE__)
void foo_method() {
  START_DECL;
  DECLARE_REC(rec1,ptr1,type_int32);
  DECLARE_REC(rec2,ptr2,type_str);
  ANONYM_REC(ptr3,type_int32);
  ...
}

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

Похоже, но не то, структурки должны быть статическими.

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

#define START_DECL(name) \
static void init##name##__COUNTER__ () {   \
   extern var_rec_t name[]; \
   ... // on-load init code placed here \
   make_list_from_array(name, sizeof(name)/sizeof(var_rec_t));
} __attribute__((constructor)); \
var_rec_t name[]={

#define INT_FIELD(value) { value, type_int32,NULL }
#define STR_FIELD(value) { value, type_str, NULL }
#define END_DECL() {0} }; 

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

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

Так утоже не работает, потому что в INT_FIELD делается еще куча всякого.

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

Нужна ли переносимость за пределы gcc/clang + GNU binutils?

Традиционное решение для таких задач — запихать метаданные по переменным в отдельную секцию (__attribute__((что-то типа section, не помню))); таким образом можно наплодить статические структуры, которые линкер сложит в непрерывную область. Дальше дело техники (определить символ, соответствующий началу секции, а также либо определить символ, соотв. концу(размеру), либо в конец вкрячить «терминатор»; тут надо решить, приятнее ли писать ld scripts навороченные, или линкеру подсовывать myvarbegin.o первым параметром и myvarend.o последним. Впрочем, сейчас может оказаться, что без того и другого можно обойтись, давно не смотрел).

Похожим образом строится список статических конструкторов / деструкторов в g++ на некоторых платформах. Параметры линуксовых ядерных модулей тоже так обрабатываются (секция .modinfo). И оно на самом деле не совсем непортабельно, обычно аналогичные возможности в других toolchain'ах находятся.

LeninGad ()

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

во многих случаях(не во всех) прокатит по типу

#define FIRST_VAR(name,ptr,type) static var_rec_t name={ptr,type,NULL}
#define NEXT_VAR(name,ptr,type) static var_rec_t name={ptr,type,[-1]&name}
в одной секции они-же (переменные) подряд складываются и адреса в принципе вычислимы и обращаться к ним можно как элементам массива

но это не просто грабли, это ГРАБЛИЩЩА..такое можно делать только увольняясь с одновременной сменой паспорта и места жительства :-)

MKuznetsov ★★★★★ ()

Непростой для понимания код, ещё более непростая концепция, по которой он построен. Дальше будет только хуже. Предлагаю вам использовать генератор кода, либо реализовать функционал «в рантайме».

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

ещё более непростая концепция, по которой он построен

давным-давно когда деревья были выше, а трава забористей, было принято C-шные файлы c SQL прогонять через ещё один препроцессор;

При известном сраче между IBM,Sybase и MS последней видимо недосталось лицензий и им пришлось выкручиваться - унаследованный код был извращённо кастрирован до макросов, а стиль пропагандировался как высокое достижение. Поэтому портянок странных макросов очень много в старом бизнес-софте, во всяких «драйверах» и «коннекторах» ODBC.

Вот ТС столкнулся с эдаким порождением ехидны

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

Экземпляр в каждом воркере - лишнее потребление озу.

Хорошо.

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

В чем мистический смысл?

Если у тебя есть gnu11, то:

int var1; char var2[100500];
typedef struct {
  void* ptr;
  int16_t type;
} var_rec_t;

var_rec_t m[] = init_list(var1, var2);//это можно раскрыть в:
var_rec_t m[] = {{&var1, typeid(typeof(var1))}, {var2, typeid(typeof(var2))}};

//Только даже этого не надо - это делается так:
typedef struct {
  void* ptr;
  int16_t type;
} var_rec_t;
var_rec_t m[] = init_list(int, char[100500]);
const var_rec_t m[] = {{&(int){0}, typeid(int)}, {(char[100500]){0}, typeid(int)}};
Безо всяких статик переменных и прочего.
init_list(var1, var2); - Строчек 100-200 макросов. Через жопу можно init_list((var1)(var2)); Строчек на 50.

gnu11 нужен для гинерика - для нормального typeid(), либо заполнять до него енум и через #define cat(a, b) a##b cat(int, _type). Тогда хватит gnu99.

Только я не понимаю зачем тебе это надо.

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.