вторник, 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)

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