Случайно наткнулся на блог норвежского разработчика Olve Maudal, а в нем на презентацию Deep C (and C++).
Интересно, очень познавательно.
Хотя лично я предпочту работать с людьми, которые не имеют столь глубоких знаний в C/C++ и, поэтому, будут писать так, чтобы только из кода было понятно, что и как работает. Как только в дело вступает глубокое знание тонких деталей стандарта или (что хуже) особенностей конкретных сред/компиляторов, тут нужно начинать бить по рукам. Если только не приходится каждый бит/такт экономить.
12 комментариев:
Недавно пришлось использовать GCCизм. Правда его использование я могу оправдать тем, что работа библиотеки по принципиальным причинам пока возможна только в среде Linux (или окружении GNU с поддержкой соответствующих syscall-ов). Однако весьма и весьма удобный. Жаль, что нет в стандарте C.
@buffovich:
Ваня, так ты бы подробнее рассказал. Наверняка не только мне будет интересно.
__attribute__ (( constructor ))
__attribute__ (( destructor ))
Святотатство может сказать кто-то... Будет отчасти прав. Я сейчас задам контекст. Упомянутая библиотека - динамически загружаемый модуль для MySQL. Если кто-то знаком с вопросом, то, наверное, знает, что MySQL при загрузке модуля сам не производит никакой его инициализации. В переносимых библиотеках подобная инициализация выполняется обычно явно пользователем с помощью "красной пилюли" init_, или _init. Однако это не тот случай. Вариантов поведения в указанном случае на самом деле не много - либо Lazy init в начале каждого экспортируемого вызова (ах да, забыл сказать, что у нас система ну пипец какая многопроцессорная, а у MySQL ну просто до неприличного много нативных потоков, поэтому в этом месте задумываемся над мьютексами), либо использовать какие-нибудь трики самого динамического загрузчика-интерпретатора (ld.so обычно). ld.so в купе с рантаймом crtbegin.o и сrtend.o обеспечивают полную поддержку формата ELF. Первый вариант из триков - использование магических точек входа void _init( void ) и void _fini( void ) для инициализации и финализации библиотеки соответственно. Хотя имена этих точек могут быть заданы пользователем в опциях линкера на стадии сборки. Указанные функции будут находится в секциях .init и .fini файла. Всё отлично, никаких мьютексов, полная потокобезопасность. Однако. Указанная возможность объявлена нерекомендуемой, т.к. у нас может получится перетереть процедуру инициализации рантайма, что приведет к большой головной боли в будущем. Второй вариант из триков - использование секций .ctors и .dtors. Эти секции обрабатываются как раз кодом рантайма и служат специально для того, чтобы вызывать конструкторы глобальных объектов (да, в С++). Указанные GCCизмы позволяют использовать такую возможность и для C. Семантика вызова достаточно проста - void myLocalInit( void ). Я делаю в каждой единице компиляции (а конкретно в её заголовочном файле) помимо всего прочего в том числе extern-объявления её конструктора и деструктора. Это очень удобно, т. к. у нас нет глобального реестра инициализаторов, который нужно править, а во вторых, простым grep-ом вычисляются модули (единицы компиляции), которые делают какую-то работу при загрузке и выгрузке плагина.
Помимо всего прочего кое-где используются вынужденно системные вызовы Linux, так что не мне не заказчику эти GCCзмы не мешают, а скорее наоборот.
Ну вот как-то так.
@buffovich:
Ситуация понятна.
А "счетчик Шварца" здесь не работает?
Женя, извини, но не могу проассоциировать C и паттерн "счётчик Шварца". Кроме того, глобальных межмодульных переменных у меня нет и использование модулями друг друга в момент инициализации запрещено большой вывеской в файле с описанием экспортируемых функций. Кроме того - там всё равно будут мьютексы на изменение счётчика. Для функций вызываемых на выборках в 1 000 000 записей проверка мьютекса - это катастрофа.
@buffovich:
Наружу so-шка может выставлять только C-шный интерфейс, унутрях же вполне можно C++ные плюшки использовать.
Счетчик Шварца использует стандартный механизм инициализации статических переменных C++ для однократного запуска кода инициализации/деинициализации объекта. И мьютексы там, насколько я себе это представляю, не нужны.
Да, я согласен. Если бы можно было C++, то ничто нам не мешало бы использовать инициализаторы глобальных инстанций и уйти от указанного выше дела.
Мьютексы нужны при Lazy init. Да, извини, не совсем понял твою идею.
@buffovich:
Ваня, просто ради любопытства: если у вас обычный Linux и обычный GCC, то что препятствует использованию C++? Или же это административный запрет заказчика?
> Хотя лично я предпочту работать с людьми, которые не имеют столь глубоких знаний в C/C++ и, поэтому, будут писать так, чтобы только из кода было понятно, что и как работает.
Я не пишу на c/c++ уже пару лет, но не увидел в презентации ничего что бы не нужно было знать.
* что такое неявная декларация
* как static влияет на видимость статической переменной за пределами модуля
* как инициализируется статическая переменная
* начальные значение автоматических переменных
* порядок выполнения операций
* упаковка структур, выравнивание
и т.д. и т.п.
@fuxx:
Тут от точки зрения зависит. Например, у человека спрашивают, что будет выведено в данном случае:
static int a;
++a;
std::cout << a << std::endl;
Наверное хорошо, если какой-то разработчик помнит, что статические данные инициализируются нулями. И исходя из этого будет считать данный пример корректными и нормальным.
Мне же спокойнее, когда человек сомневается в своих знаниях и из-за этого пишет
static int a = 0;
После чего становится уже не важно, статическая ли а или нет, соответствует ли компилятор стандарту или нет и т.д.
@Евгений Охотников пишет...
я бы тоже предпочел читать
static int a = 0;
это еще и к копипасту... ой, я хотел сказать к рефакторингу устойчивее, чем
static int a;
а если речь вести о языке в целом, то подход D -- а именно инициализировать все, и требовать спец. синтаксис для отстутствия инициализации -- емнип,
int a = void;
-- выглядит лучше
Отправить комментарий