вторник, 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 системных вызовов.

5 коммент.:

Анонимный комментирует...

В данном случае переменная удалена не потому, что она const, а потому что static:
static const struct console sConsole;

В описании переменных и процедур на глобальном уровне, static имеет свойства "локального идентификатора", что позволяет при отсутствии его использования удалить. Без static, все идентификаторы являются экспортируемыми для внешней линковки, поэтому никогда не удаляются.

Андрей Валяев комментирует...

Нет, он используется!!!

if (sConsole != NULL)
    sConsole->putc (c);

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

Тоже хорошая практика - ставить static.

Андрей Валяев комментирует...

Забыл сказать - изначально они не были const, и обе располагались в секции данных. после дописывания const - структура переместилась в rodata, а указатель вообще исчез.

Анонимный комментирует...

Всё ясно. А нафига тогда sConsole?

Андрей Валяев комментирует...

На будующее... :)

Пока консоль только cga/ega/vga-шная, но планирую добавить еще mda-шную, серийную и нулевую (ничего не пишущую)

Будет выбираться из командной строки.