Доступ к массиву оценивает через адресную арифметику с указателями по сравнению с индексированием в C

Указатель NULL - это тот, который указывает на никуда. Когда вы разыскиваете указатель p, вы говорите «дайте мне данные в месте, хранящемся в« p ». Когда p является нулевым указателем, местоположение, хранящееся в p, является nowhere, вы говорите «Дайте мне данные в месте« нигде ». Очевидно, он не может этого сделать, поэтому он выбрасывает NULL pointer exception.

В общем, это потому, что что-то не было правильно инициализировано.

38
задан Peter Mortensen 19 March 2012 в 15:15
поделиться

10 ответов

Необходимо понять причину позади этого требования. Вы когда-либо опрашивали себя, почему это быстрее? Давайте сравним некоторый код:

int i;
int a[20];

// Init all values to zero
memset(a, 0, sizeof(a));
for (i = 0; i < 20; i++) {
    printf("Value of %d is %d\n", i, a[i]);
}

Они - весь нуль, какое удивление :-P Вопрос, что средства a[i] на самом деле в низкоуровневом машинном коде? Это означает

  1. Возьмите адрес a в памяти.

  2. Добавить i времена размер единственного объекта a к тому адресу (интервал обычно составляет четыре байта).

  3. Выберите значение от того адреса.

Так каждый раз Вы выбираете значение от a, базовый адрес a добавляется к результату умножения i четыре. Если Вы просто разыменовываете указатель, шаг 1. и 2. не должны быть выполнены, только шаг 3.

Рассмотрите код ниже.

int i;
int a[20];
int * b;

memset(a, 0, sizeof(a));
b = a;
for (i = 0; i < 20; i++) {
    printf("Value of %d is %d\n", i, *b);
    b++;
}

Этот код мог бы быть быстрее..., но даже если это, различие является крошечным. Почему это могло бы быть быстрее? "*b" совпадает с шагом 3. из вышеупомянутых. Однако "b ++" не то же как шаг 1. и шаг 2. "b ++" увеличит указатель на 4.

(важный для новичков: выполнение ++ на указателе не увеличит указатель один байт в памяти! Это увеличит указатель на столько же байтов в памяти сколько данные, на которые это указывает, находится в размере. Это указывает на int и int четыре байта на моей машине, таким образом, b ++ увеличивает b на четыре!)

Хорошо, но почему это могло бы быть быстрее? Поскольку добавление четыре к указателю быстрее, чем умножение i четыре и добавление этого к указателю. У Вас есть дополнение в любом случае, но во втором, у Вас нет умножения (Вы избегаете процессорного времени, необходимого для одного умножения). Рассмотрение скорости современных центральных процессоров, даже если массив был 1 элементом mio, интересно, могли ли Вы действительно сравнить различия, все же.

То, что современный компилятор может оптимизировать любой, чтобы быть одинаково быстрым, является чем-то, что можно проверить рассмотрение блока, производит его, производит. Вы делаете так путем передачи "-S" опции (капитал S) к GCC.

Вот код первого кода C (уровень оптимизации -Os использовался, что означает, оптимизируют для размера кода и скорости, но не делают оптимизации скорости, которая увеличит размер кода заметно, в отличие от этого, -O2 и очень в отличие от этого, -O3):

_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    subl    $108, %esp
    call    ___i686.get_pc_thunk.bx
"L00000000001$pb":
    leal    -104(%ebp), %eax
    movl    $80, 8(%esp)
    movl    $0, 4(%esp)
    movl    %eax, (%esp)
    call    L_memset$stub
    xorl    %esi, %esi
    leal    LC0-"L00000000001$pb"(%ebx), %edi
L2:
    movl    -104(%ebp,%esi,4), %eax
    movl    %eax, 8(%esp)
    movl    %esi, 4(%esp)
    movl    %edi, (%esp)
    call    L_printf$stub
    addl    $1, %esi
    cmpl    $20, %esi
    jne L2
    addl    $108, %esp
    popl    %ebx
    popl    %esi
    popl    %edi
    popl    %ebp
    ret

То же со вторым кодом:

_main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %edi
    pushl   %esi
    pushl   %ebx
    subl    $124, %esp
    call    ___i686.get_pc_thunk.bx
"L00000000001$pb":
    leal    -104(%ebp), %eax
    movl    %eax, -108(%ebp)
    movl    $80, 8(%esp)
    movl    $0, 4(%esp)
    movl    %eax, (%esp)
    call    L_memset$stub
    xorl    %esi, %esi
    leal    LC0-"L00000000001$pb"(%ebx), %edi
L2:
    movl    -108(%ebp), %edx
    movl    (%edx,%esi,4), %eax
    movl    %eax, 8(%esp)
    movl    %esi, 4(%esp)
    movl    %edi, (%esp)
    call    L_printf$stub
    addl    $1, %esi
    cmpl    $20, %esi
    jne L2
    addl    $124, %esp
    popl    %ebx
    popl    %esi
    popl    %edi
    popl    %ebp
    ret

Ну, это отличается, это наверняка. 104 и 108 различий в числе вышли из переменной b (в первом коде была одна переменная меньше на стеке, теперь у нас есть еще один, изменяя адреса стека). Реальное различие в коде в for цикл

movl    -104(%ebp,%esi,4), %eax

по сравнению с

movl    -108(%ebp), %edx
movl    (%edx,%esi,4), %eax

На самом деле мне скорее похоже, что первый подход быстрее (!), так как это выпускает один машинный код ЦП для выполнения всей работы (ЦП делает все это для нас), вместо того, чтобы иметь два машинных кода. С другой стороны, две команды блока ниже могли бы иметь более низкое время выполнения в целом, чем то выше.

Как заключительное слово, я сказал бы в зависимости от Вашего компилятора и возможностей ЦП (что управляет предложением центральных процессоров получить доступ к памяти, каким образом), результат мог бы быть так или иначе. Любой мог бы быть более быстрым/медленнее. Вы не можете сказать наверняка, если Вы не ограничиваете себя точно одним компилятором (значение также одной версии) и один определенный ЦП. Поскольку центральные процессоры могут сделать все больше в единственной команде блока (давным-давно, компилятор действительно должен был вручную выбрать адрес, умножиться i четыре и добавляют обоих вместе прежде, чем выбрать значение), операторы, которые раньше были абсолютной истиной давным-давно, в наше время все более сомнительны. Также, кто знает, как центральные процессоры работают внутренне? Выше я сравниваю инструкции по сборке с двумя другими.

Я вижу, что количество инструкций отличается, и время, в котором нужна такая инструкция, может отличаться также. Также то, в каком количестве памяти эти инструкции нужны в их презентации машины (они должны быть переданы из памяти кэшу ЦП, в конце концов), отличается. Однако современные центральные процессоры не выполняют инструкции путем, Вы подаете их. Большие инструкции разделения (часто называемый CISC) в маленькие подынструкции (часто называемый RISC), который также позволяет им лучше оптимизировать процесс выполнения программы для скорости внутренне. На самом деле первая, единственная инструкция и две других инструкции ниже могли бы привести к тому же набору подынструкций, в этом случае вообще нет никакого измеримого различия в скорости.

Относительно Objective C это просто C с расширениями. Таким образом, все, что сохраняется для C, будет сохраняться для Objective C также с точки зрения указателей и массивов. Если Вы используете Объекты, с другой стороны (например, NSArray или NSMutableArray), это - совершенно другой зверь. Однако в этом случае необходимо получить доступ к этим массивам с методами так или иначе, нет никакого указателя/доступа к массиву для выбора из.

74
ответ дан Peter Mortensen 27 November 2019 в 03:13
поделиться

"адресная арифметика с указателями использования обычно быстрее, чем индексирование для доступа к массиву"

Nah. Это - та же операция так или иначе. Индексирование является синтаксическим сахаром для добавления (размер элемента * индекс) к начальному адресу массива.

Однако при итерации по элементам в массиве, взятии указателя на первый элемент и увеличении его каждый раз через цикл обычно будет немного быстрее, чем вычисление положения элемента тока от переменной цикла каждый раз. (Хотя для этого необычно иметь значение очень в реальном применении. Исследуйте свой алгоритм, первая, преждевременная оптимизация является корнем всего зла, и т.д. и т.д.)

10
ответ дан moonshadow 27 November 2019 в 03:13
поделиться

Это могло бы быть, немного вне темы (извините), потому что это не отвечает на Ваш вопрос относительно скорости выполнения, но необходимо полагать, что преждевременная оптимизация является корнем всего зла (Knuth). По-моему, особенно когда все еще (ре), учащее язык, любой ценой запишите ему способ, которым является самым легким читать сначала. Затем если Ваши прогоны программы корректный , рассмотрите скорость оптимизации. Большую часть времени Вы кодируете, будет достаточно быстро так или иначе.

5
ответ дан TheMarko 27 November 2019 в 03:13
поделиться

Mecki имеет большое объяснение. На основе моего опыта одна из вещей, которая часто имеет значение с индексацией по сравнению с указателями, - то, что другой код находится в цикле. Пример:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <iostream>

using namespace std;

typedef int64_t int64;
static int64 nsTime() {
  struct timespec tp;
  clock_gettime(CLOCK_REALTIME, &tp);
  return tp.tv_sec*(int64)1000000000 + tp.tv_nsec;
}

typedef int T;
size_t const N = 1024*1024*128;
T data[N];

int main(int, char**) {
  cout << "starting\n";

  {
    int64 const a = nsTime();
    int sum = 0;
    for (size_t i=0; i<N; i++) {
      sum += data[i];
    }
    int64 const b = nsTime();
    cout << "Simple loop (indexed): " << (b-a)/1e9 << "\n";
  }

  {
    int64 const a = nsTime();
    int sum = 0;
    T *d = data;
    for (size_t i=0; i<N; i++) {
      sum += *d++;
    }
    int64 const b = nsTime();
    cout << "Simple loop (pointer): " << (b-a)/1e9 << "\n";
  }

  {
    int64 const a = nsTime();
    int sum = 0;
    for (size_t i=0; i<N; i++) {
      int a = sum+3;
      int b = 4-sum;
      int c = sum+5;
      sum += data[i] + a - b + c;
    }
    int64 const b = nsTime();
    cout << "Loop that uses more ALUs (indexed): " << (b-a)/1e9 << "\n";
  }

  {
    int64 const a = nsTime();
    int sum = 0;
    T *d = data;
    for (size_t i=0; i<N; i++) {
      int a = sum+3;
      int b = 4-sum;
      int c = sum+5;
      sum += *d++ + a - b + c;
    }
    int64 const b = nsTime();
    cout << "Loop that uses more ALUs (pointer): " << (b-a)/1e9 << "\n";
  }
}

В быстрой основанной на Core 2 системе (g ++ 4.1.2, x64), вот синхронизация:

    Simple loop (indexed): 0.400842
    Simple loop (pointer): 0.380633
    Loop that uses more ALUs (indexed): 0.768398
    Loop that uses more ALUs (pointer): 0.777886

Иногда индексация быстрее, иногда адресная арифметика с указателями. Это зависит от, как ЦП и компилятор в состоянии конвейерно обработать выполнение цикла.

4
ответ дан Mr Fooz 27 November 2019 в 03:13
поделиться

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

Теперь, если Вы имеете дело явно с блоком данных Вы malloc () 'd и Вы хотите получить указатель в тех данных, сказать 20 байтов в заголовке звукового файла, тогда я думаю, что адресная арифметика более ясно выражает то, что Вы пытаетесь сделать.

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

РЕДАКТИРОВАНИЕ: Согласно некоторым из этих других ответов, индексирование является просто syntacitic элементом и не имеет никакого эффекта на производительность как, я фигурировал. В этом случае определенно пойдите с любым контекстом, который Вы пытаетесь выразить через данные доступа в блоке, на который указывает указатель.

3
ответ дан Bob Somers 27 November 2019 в 03:13
поделиться

Следует иметь в виду, что скорость выполнения трудно предсказать, смотря на машинный код с суперскалярными CPU и т.п. с

  • неисправный exection
  • , конвейерно обрабатывающий
  • предсказание ветвлений
  • гиперпоточность
  • ...

Это только считает машинные команды и даже только не считает циклы часов. Кажется легче только иметь размеры в случаях в случае необходимости. Даже если не невозможно вычислить, корректный цикл значат данную программу (мы должны были сделать это в университете), но это едва забавно и твердо разобраться. Заметка на полях: Измерение правильно также трудно в многопоточном / среды mulit-процессора.

3
ответ дан TheMarko 27 November 2019 в 03:13
поделиться
char p1[ ] = "12345";
char* p2 = "12345";

char *ch = p1[ 3 ]; /* 4 */
ch = *(p2 + 3); /* 4 */

в стандарте C не говорится, который быстрее. На заметном поведении то же, и это до компилятора для реализации его всегда, это хочет. Как правило, это даже не считает память вообще.

В целом, у Вас нет способа сказать, который "быстрее", если Вы не определяете компилятор, версию, архитектуру, и компилируете опции. Даже тогда оптимизация будет зависеть от окружающего контекста.

, Таким образом, общие рекомендации должны использовать то, что дает более ясный и более простой код. Используя массив [я] даю некоторую способность к инструментам попытаться найти index-out-of-bound условия, поэтому если Вы используете массивы, лучше просто рассматривать их как таковой.

, Если это очень важно - изучают ассемблер, который Вы компилятор генерируете. Но имейте в виду, что это может измениться, как Вы изменяете код, который окружает его.

1
ответ дан paxdiablo 27 November 2019 в 03:13
поделиться

Нет, использование адресной арифметики с указателями не быстрее и по всей вероятности медленнее, потому что оптимизирующий компилятор может использовать инструкции как ЛЕА (Исполнительный адрес Загрузки) на процессорах Intel или подобный на других процессорах для адресной арифметики с указателями, которая быстрее, чем добавляют или add/mul. Это имеет преимущество выполнения нескольких вещей сразу и не осуществления флагов, и также требуется один цикл для вычисления. BTW, ниже из руководства GCC. Так -Os не оптимизирует, прежде всего, для скорости.

я также полностью согласен с themarko. Сначала попытайтесь записать чистый, читаемый и повторно используемый код и затем думать об оптимизации и использовать некоторые профильные инструменты для нахождения узкого места. Большую часть времени проблемой производительности является связанный ввод-вывод или некоторый плохой алгоритм или некоторая ошибка, которую необходимо выследить. Knuth является человеком;-)

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

-Os Оптимизируют для размера. Os включает всю оптимизацию O2, которая обычно не увеличивает размер кода. Это также выполняет дальнейшую оптимизацию, разработанную для сокращения размера кода.

1
ответ дан Peter Mortensen 27 November 2019 в 03:13
поделиться

Маловероятно, что будет любое различие в скорости.

Используя оператор массива [], вероятно, предпочтен, как в C++ можно использовать тот же синтаксис с другими контейнерами (например, вектор).

0
ответ дан MrZebra 27 November 2019 в 03:13
поделиться

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

0
ответ дан Peter Mortensen 27 November 2019 в 03:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: