Сишные трюки от мыщъха

       

оптимизированный вариант


 

операции разыменования: префиксы намного выгоднее по сравнению с постфиксов при разыменовании, в результате чего код while(*++p) существенно эффективнее чем while(*p++), во всяком случае на платформе x86 и, по всей видимости x86-64 (к сожалению, в силу отсутствия железа проверить возможности не было). Однако, операциями разыменования смешивать с постфиксами и префиксами следует _крайней_ осторожно, иначе можно получить очень неожиданный результат (см. "неудачный выбор приоритетов в Си"). При работе на x86 (с распространёнными компиляторами) использование индексов эффективнее сдвига указателей. Не всегда, но очень часто. То есть, код типа "for(a=0;a<len;a++) *dst++ = *src++;" гораздо сложнее оптимизируется, чем "for(a=0;a<len;a++) dst[a] = src[a];", хотя качественный оптимизатор в обоих случаях должен сгенерировать идентичный машинный код.

 

неудачный выбор приоритетов в Си: вопреки "здравому смыслу" конструкция типа *p[a]++ увеличивает отнюдь _не_ содержимое ячейки, на которую указывает *(p+a), а значение самого указателя p! Для достижения ожидаемого результата необходимо либо явно навязать наше намерение компилятору путем расстановки скобок: "(*p)[a]++;", либо же вовсе отказаться от оператора "++", заменив его оператором "+=" и тогда наш код будет выглядеть так: "*p[a]+=1;"

Представляется интересным докопаться до _сути_ происходящего. Ведь основное кредо Си - краткость. Чего стоит один неявный 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");

}


Содержание раздела