Каковы убедительные примеры, где арифметика указателей предпочтительнее, чем подписка на массив? - PullRequest
9 голосов
/ 28 марта 2009

Я готовлю несколько слайдов для вводного класса C и пытаюсь представить хорошие примеры (и мотивацию) для использования арифметики указателей над подпиской массива.

Многие примеры, которые я вижу в книгах, довольно эквивалентны. Например, во многих книгах показано, как изменить регистр всех значений в строке, но за исключением замены a [i] на a * p, код идентичен.

Я ищу хороший (и короткий) пример с одномерными массивами, где арифметика указателей может дать значительно более элегантный код. Есть идеи?

Ответы [ 9 ]

14 голосов
/ 28 марта 2009

Снова получить указатель вместо значения:

Обычно используют арифметику указателей, когда хотят снова получить указатель. Чтобы получить указатель при использовании индекса массива: вы 1) вычисляете смещение указателя, затем 2) получаете значение в этой ячейке памяти, затем 3) вам нужно использовать &, чтобы получить адрес снова. Это более типичный и менее чистый синтаксис.

Пример 1: Допустим, вам нужен указатель на 512-й байт в буфере

char buffer[1024]
char *p = buffer + 512;

Чище, чем:

char buffer[1024];
char *p = &buffer[512];

Пример 2: Более эффективный strcat

char buffer[1024];
strcpy(buffer, "hello ");
strcpy(buffer + 6, "world!");

Это чище, чем:

char buffer[1024];
strcpy(buffer, "hello ");
strcpy(&buffer[6], "world!");

Использование арифметики указателей ++ в качестве итератора:

Увеличение указателей с помощью ++ и уменьшение с помощью - полезно при итерации по каждому элементу в массиве элементов. Это чище, чем использование отдельной переменной, используемой для отслеживания смещения.


Вычитание указателя:

Вы можете использовать вычитание указателя с арифметикой указателя. В некоторых случаях это может быть полезно, чтобы получить элемент до того, на который вы указываете. Это можно сделать и с помощью подписчиков массива, но это выглядит очень плохо и запутанно. Особенно программисту на питоне, где дается отрицательный индекс для индексации чего-либо из конца списка.

3 голосов
/ 28 марта 2009
char *my_strcpy(const char *s, char *t) {
  char *u = t;
  while (*t++ = *s++);
  return u;
}

Почему вы хотите испортить такую ​​красоту индексом? (См. K & R и как они основываются на этом стиле.) Есть причина, по которой я использовал вышеуказанную подпись такой, какая она есть. Прекратите редактирование, не прося разъяснения сначала. Для тех, кто считает, что знает, посмотрите настоящую подпись - вы пропустили несколько restrict квалификаций.

Тестирование выравнивания структуры и реализация макроса offsetof.

2 голосов
/ 15 октября 2009

Арифметика указателей может выглядеть причудливо и «хакерски», но я никогда не сталкивался с тем, чтобы это было БЫСТРЕЕ, чем стандартное индексирование. Наоборот, я часто сталкивался со случаями, когда это сильно замедляло код.

Например, типичный последовательный цикл по массиву с указателем может быть менее эффективным, чем цикл с классическим индексом на современных процессорах, которые поддерживают расширения SSE. Арифметика указателей в цикле в достаточной степени блокирует компиляторы от выполнения векторизации цикла, что может привести к типичному увеличению производительности в 2–4 раза. Кроме того, использование указателей вместо простых целочисленных переменных может привести к ненужным операциям сохранения памяти из-за псевдонимов указателей.

Итак, обычно НИКОГДА не рекомендуется использовать арифметику указателей вместо стандартного индексированного доступа.

1 голос
/ 28 марта 2009

Что-то забавное. Надеюсь, вам никогда не придется иметь дело: указатели могут создавать псевдонимы, а массивы - нет. Псевдонимы могут вызывать все виды генерации неидеального кода, наиболее распространенным из которых является использование указателя в качестве параметра out для другой функции. По сути, компилятор не может предположить, что указатель, используемый функцией, не имеет псевдонима или чего-либо еще в этом стековом фрейме, поэтому он должен перезагружать значение из указателя при каждом его использовании. Точнее, на всякий случай.

1 голос
/ 28 марта 2009

Вы спрашиваете конкретно о C, но C ++ также опирается на это:

Большая часть арифметики указателей естественным образом обобщает концепцию прямого итератора. Прогулка по памяти с *p++ может использоваться для любого упорядоченного контейнера (связанный список, список пропуска, вектор, двоичное дерево, дерево B и т. Д.), Благодаря перегрузке оператора.

1 голос
/ 28 марта 2009

Если вы используете старый компилятор или какой-то специализированный компилятор встроенных систем, может иметь небольшие различия в производительности, но большинство современных компиляторов, вероятно, оптимизируют эти (крошечные) различия.

Следующая статья может быть чем-то, на что вы могли бы опираться - зависит от уровня ваших учеников:

http://geeks.netindonesia.net/blogs/risman/archive/2007/06/25/Pointer-Arithmetic-and-Array-Indexing.aspx

1 голос
/ 28 марта 2009

итерация по двумерному массиву, в котором положение элемента данных не имеет большого значения
если вы не используете указатели, вам придется отслеживать двух подписчиков
с помощью указателей вы можете указывать на верхнюю часть вашего массива, а с помощью одного цикла - проходить через все это

0 голосов
/ 18 августа 2011
#include ctype.h
void skip_spaces( const char **ppsz )
{
  const char *psz = *ppsz;
  while( isspace(*psz) )
    psz++;
  *ppsz = psz;
}

void fn(void)
{
  char a[]="  Hello World!";
  const char *psz = a;
  skip_spaces( &psz );
  printf("\n%s", psz);
}
0 голосов
/ 28 марта 2009

Часто выбор только один из стилей - один выглядит или чувствует себя более естественным, чем другой для конкретного случая.

Существует также аргумент, что использование индексов может привести к тому, что компилятору придется многократно пересчитывать смещения внутри цикла - я не уверен, как часто это происходит (кроме неоптимизированных сборок), но я представляю это случается, но это, вероятно, редко проблема.

Одна область, которая, на мой взгляд, важна в долгосрочной перспективе (которая может не относиться к вводному классу C - но я говорю об этом раньше, я говорю) - это использование арифметики с указателями применимо к идиомам, используемым в C ++ STL. Если вы научите их понимать арифметику указателей и будете их использовать, то, когда они перейдут к STL, у них появится возможность правильно использовать итераторы.

...