вторник, 26 февраля 2008 г.

Разворот стека методом трассировки кода (описание)

Предпринял попытку описать разворот стека методом трассировки кода, заодно проверил функционирование wiki на Google code. Они конечно хитрые, хранят wiki документы непосредственно в subversion репозитории. Портят мне логи, хотя по большому счету это разумный ход.

Это кстати новость. Перенес проект под Google code. Ну Subversion - он и в Африке Subversion, а вот Downloads сделан гораздо лучше. Да и wiki - это плюс. Что еще нужно от проектного хостинга по большому счету...

четверг, 21 февраля 2008 г.

Статическая гимнастика...

Как-то раз я ломал голову над тем, как же мне в чистом си проверить размер структуры в процессе компиляции программы. sizeof не работает в условиях #if. Тогда я почему-то так ничего и не придумал, а стоило бы поискать...

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

Способ первый:

#define STATIC_ASSERT(exp) extern char __static_assert[(exp) ? 1 : -1]

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

strict foo {...};
STATIC_ASSERT (sizeof (struct foo) == xxx);

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

Встречаются и менее универсальные способы - например:

#define STATIC_ASSERT(exp) switch (0) { case 0: case ((exp) ? 1 : 0): break; }

Но такие ассерты ограничены в области применения. Их нельзя будет использовать за пределами функций. Думаю что так же не стоит пытаться оборачивать первый вариант традиционным для макросов do { } while (0), Это так же сократит возможности его использования.

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

Для большей наглядности вместо соответствующего стандарту си - assert(exp) создал STUB_ASSERT(exp, msg), который в случае нарушения ругается не просто выражениями, а целыми предложениями. Заодно можно держаться в ядре немного подальше от стандарта, чтобы не конфликтовать с ним.

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

#define assert(exp) STUB_ASSERT((exp) && 'FOOF', #exp)

Выражение 'FOOF' является допустимой для gcc многосимвольной константой, на которую выдается предупреждение 'Page.c:44:38: warning: multi-character character constant'. Можно конечно побаловаться с другими вариантами предупреждающих выражений, но в любом случае большой наглядности на этапе компиляции добиться не получится. Предупреждение и без того весьма примечательное.

вторник, 19 февраля 2008 г.

О практической пользе константных параметров...

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

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

KernelWait:
pushfl
pushl %ebx
pushl %ecx
pushl %edx
pushl %esi
pushl %edi
pushl %ebp

pushl %ds
pushl %es
movl $KERNEL_DATA_SELECTOR, %ebx
movw %bx, %ds
movw %bx, %es

pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
call StubWait
add $16, %esp

popl %ds
popl %es

popl %ebp
popl %edi
popl %esi
popl %edx
popl %ecx
popl %ebx
popfl

iret

Глядя на эту функцию часть кода может показаться лишней. Вполне логично было бы написать так:

KernelWait:
...
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
call StubWait
add $4, %esp
popl %edx
popl %ecx
popl %ebx
...
iret

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

int StubWait (int a, int b, int c, int d)
{
d += 10;

Очень плохо! Разумным способом предотвращения данной ситуации является описание всех параметров константными.

int StubWait (const int a, const int b, const int c, const int d)
{
int dnew = d + 10;

Так значительно лучше и не грозит проблемами. Кроме того думаю, что привычка объявлять все методы функций константными даст компилятору больший простор для оптимизации. Это предположение, но компиляторы сейчас умные, обявление константости в следующем случае:

static const
struct console StubXGAConsole = {
.putc = StubXGAPutC,
.getc = StubXGAGetC,
};

static const
struct console *sConsole = &StubXGAConsole;

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

PS: А экономия на push/pop очень напоминает преждевременную оптимизацию, к тому же нелогично после вызова функции пупать параметры обратно, поэтому лучше я воздержусь от такого вида оптимизации. Тем более что я использую единственный макрос для всех 8 системных вызовов.

пятница, 15 февраля 2008 г.

Еще немного про in-charge конструкторы...

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

class foo;
class bar : public foo;
class cut : public bar;


Или как там обычно называют третий класс для примера?

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

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

0804855a <_ZN3barC2Ev>:
804855a: 8b 44 24 04 mov 0x4(%esp),%eax
804855e: c7 00 70 86 04 08 movl $0x8048670,(%eax)
8048564: c3 ret

08048584 <_ZN3barC1Ev>:
8048584: 8b 44 24 04 mov 0x4(%esp),%eax
8048588: c7 00 70 86 04 08 movl $0x8048670,(%eax)
804858e: c3 ret

Но нету никакой разницы.
Вероятно стоит с этим вопросом всетаки обратиться в comp.lang.c++, или наверное еще лучше к кому нибудь из группы gcc.

Может быть множественое наследование???
class foo;
class bar;
class cut : public bar, public foo;


Но нет, опять никакой разницы... что-то я наверное не понимаю :)
Может быть содержимого прикрутить? Опять не то.

А-а-а, ЭВРИКА! нашел, нашел... :)
В случае виртуального наследования конструкторы получаются разными!

class foo;
class bar : virtual public foo;

08048522 <_ZN3barC1Ev>:
8048522: 8b 44 24 04 mov 0x4(%esp),%eax
8048526: c7 00 fc 85 04 08 movl $0x80485fc,(%eax)
804852c: c3 ret

08048514 <_ZN3barC2Ev>:
8048514: 8b 44 24 08 mov 0x8(%esp),%eax
8048518: 8b 10 mov (%eax),%edx
804851a: 8b 44 24 04 mov 0x4(%esp),%eax
804851e: 89 10 mov %edx,(%eax)
8048520: c3 ret


not-in-charge конструктор _ZN3barC1Ev не обращает внимания на виртуальность наследования, в то время как in-charge конструктор _ZN3barC2Ev получает два параметра (два указателя this вероятно), и похоже переписывает указатель на vtbl из родительского класса в свой, хотя я не использовал виртуальные функции а данном эксперименте, но видимо виртуальное наследование автоматически приводит к появлению vtbl.

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

вторник, 5 февраля 2008 г.

not-in-charge конструкторы.

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

Как выяснилось, третий тип, complete object allocating constructor, никогда не используется. И на том спасибо, а то это была бы тайна трех конструкторов..

В терминологии gcc эти конструкторы называются in-charge и not-in-charge конструкторами. Разница между ними заключается в том, что not-in-charge конструктор конструирует только поля данного класса, не вызывая родительских конструкторов. В то время как in-charge конструктор конструирует объект полностью.

Использование в дочерних конструкторах not-in-charge конструкторов базовых классов позволяет избежать многочисленных инициализаций. Скорее всего это актуально для множественного наследования. Подробнее можно прочитать здесь.

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

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

Мне удалось избежать дублирования, описав тело конструктора в описании класса (inline конструктор), при этом он всеравно оформился в виде функции, но теперь уже однократно.

00000000 W _ZN15RandomGeneratorC1Ev

Вот пожалуй и все...

понедельник, 4 февраля 2008 г.

Разгадка тайны двух конструкторов.

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

gcc при генерации конструкторов, да и дестркуторов оперирует следующими понятиями: Полный конструктор объекта (complete object constructor), маркируется как C1. Этот конструктор вызывается при создании объекта данного класса; Конструктор базового объекта (base object constructor) - C2. Этот конструктор вызывается из конструкторов производных классов; Существует еще третий тип, как это по русски, полный выделяющий конструктор (complete allocating constructor), он маркируется как C3, но не знаю точно, в каких случаях используется.

Аналогичная ситуация и с деструкторами, почти... Деструктор удаления (deleting destructor) - D0, полный деструктор объекта (complete object destructor) - D1, деструктор базового объекта (base object destructor) - D2.

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

Я что-то так вот сходу даже не могу придумать чем родительский конструктор отличается от конкретного. Но разработчикам gcc видимо виднее.

Мне надо придумать только как избавиться от лишней копии. Класс случайно не воспримет слово static?

Тайна двух конструкторов...

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

Надо сказать, что я не очень люблю загадки. Я предпочитаю иметь полный контроль над кодом. И когда происходит что-то, чего я не понимаю, я начинаю нервничать, проклинать тот день, когда я решил переписать ядро частично на c++, и вообще...

class RandomGenerator {
public:
RandomGenerator ();
...
};

Ничем не примечательный класс, однако в коде мы видим следующее...

001039ac T RandomGenerator::RandomGenerator()
001039cc T RandomGenerator::RandomGenerator()

Начинаем выяснять почему...

$ nm Kernel-IA32
...
001039ac T _ZN15RandomGeneratorC1Ev
001039cc T _ZN15RandomGeneratorC2Ev

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

$ objdump -d Kernel-IA32
Disassembly of section .text:

001039ac <_ZN15RandomGeneratorC1Ev>:
1039ac: 53 push %ebx
1039ad: 83 ec 10 sub $0x10,%esp
1039b0: 8b 5c 24 18 mov 0x18(%esp),%ebx
1039b4: 68 71 15 00 00 push $0x1571
1039b9: 53 push %ebx
1039ba: e8 6f fe ff ff call 10382e <_ZN15RandomGenerator4initEm>
1039bf: 89 5c 24 20 mov %ebx,0x20(%esp)
1039c3: 83 c4 18 add $0x18,%esp
1039c6: 5b pop %ebx
1039c7: e9 70 fd ff ff jmp 10373c <_ZN15RandomGenerator6reloadEv>

001039cc <_ZN15RandomGeneratorC2Ev>:
1039cc: 53 push %ebx
1039cd: 83 ec 10 sub $0x10,%esp
1039d0: 8b 5c 24 18 mov 0x18(%esp),%ebx
1039d4: 68 71 15 00 00 push $0x1571
1039d9: 53 push %ebx
1039da: e8 4f fe ff ff call 10382e <_ZN15RandomGenerator4initEm>
1039df: 89 5c 24 20 mov %ebx,0x20(%esp)
1039e3: 83 c4 18 add $0x18,%esp
1039e6: 5b pop %ebx
1039e7: e9 50 fd ff ff jmp 10373c <_ZN15RandomGenerator6reloadEv>

Они полностью идентичны за исключением смещений. Загадка однако. Стоит отметить, что других конструкторов тоже два.

0010388c T RandomGenerator::RandomGenerator(unsigned long const*, unsigned long)
001039ec T RandomGenerator::RandomGenerator(unsigned long const*, unsigned long)

Как с этим бороться?