Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #91, ИЮЛЬ 2006 г.

Трюки от Криса

Крис Касперски

Хакер, номер #091, стр. 091-132-2


Представляется интересным докопаться до сути происходящего, ведь основное кредо Си - краткость. Чего стоит один неявный int, который попил много крови разработчикам компиляторов. И тут... вдруг сталкиваешься с таким расточительством! Ведь чтобы использовать '*', надо ставить скобки, а это - целых два нажатия на клаву. Зачем? Может быть, есть такие ситуации, где именно такой расклад приоритетов дает выигрыш? О чем вообще думали в этот момент разработчики языка? В доступных мне книжках никаких вразумительных объяснений ситуации я так и не нашел.

Прозрение наступило внезапно, и причина, как выяснилась, оказалась даже не в самом языке, а в особенностях косвенной автоинкрементной/автодекрементной адресации процессора PDP-11, из которого, собственно, и вырос Си. Команда типа «MOV @(p)+, xxx» пересылает содержимое **p в xxx и затем увеличивает значение p. Да! Именно p, а отнюдь не ячейки, на которую **p ссылается!!!

Так стоит ли удивляться тому, что люди, взращенные на идеологии PDP-11, перенесли ее поведение и на разрабатываемый ими язык? И, кстати, о птичках. Система адресации PDP-11 намного мощнее, удобней и элегантнее того уродства, что реализовано в x86...

Хочешь испытать свой компилятор? Нет проблем! Вот довольно познавательный листинг:

main()

{

char buf; char* p_buf[2]; char **p;

#define INIT buf=0x66; *p_buf=&buf; *(p_buf+1)=&buf; p=&p_buf;

INIT;

printf("char **p;\n");

printf("p = %p; *p = %p; **p = %x\n\n",p, *p, **p);

*p[0]++; printf("*p[0]++;\n");

printf("p = %p; *p = %p; **p = %x\n",p, *p, **p);

printf("смотрите, увеличилось не содержимое **p,\n");

printf("а указатель, на который ссылается *p!\n");

printf("то есть мы получили совсем не то, что хотели!\n\n");

INIT;

(*p)[0]++; printf("(*p)[0]++;\n");

printf("p = %p; *p = %p; **p = %x;\n",p, *p, **p);

printf("хорошо, заключаем *p в скобки, тем самым явно\n");

printf("навязывая компилятору последовательность действий\n\n");

INIT;

*p[0]+=1; printf("*p[0]+=1;\n");

printf("p = %p; *p = %p; **p = %x;\n",p, *p, **p);

printf("забавно, но замена оператора ++ на оператор +=\n");

printf("эту проблему как рукой снимает!\n");

}

Для преодоления катастрофической нехватки регистров некоторые компиляторы стремятся совмещать счетчик цикла с указателем на обрабатываемые данные. Код вида «for (i = 0; i < n; i++) n+=a[i];» трансформируется оптимизатором в «for (p= a; p < &a[n]; p++) n+=*p;» Экономия налицо! Вместо четырех переменных после преобразования осталось всего лишь три!

Впервые (насколько мне известно) эта техника использовалась в компиляторах фирмы Hewlett-Packard, где она фигурировала под термином register reassociation. А что же конкуренты?! Возьмем следующий код (кстати, выдранный из документации на HP-компилятор):

Назад на стр. 091-132-1  Содержание  Вперед на стр. 091-132-3