Трюки от Криса Крис Касперски Хакер, номер #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-компилятор): |