оптимизированный вариант
операции разыменования: префиксы намного выгоднее по сравнению с постфиксов при разыменовании, в результате чего код 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");
}