2010-07-13 3 views
42

Когда мы вычитаем указатель из другого указателя, разница не равна количеству байтов, которые они разделены, но равным количеству целых чисел (если указывать на целые числа), они отделены друг от друга. Почему так?Путаница вычитания указателя

+52

Чтобы ввести крошечное зерно здравомыслия? –

+1

Динт: S? –

+7

это абстракция. Таким образом, вам не нужно знать размер объекта, на который указывает указатель, вы можете положиться на то, что p-- или p ++ перейдут к последнему или следующему объекту. –

ответ

62

Идея заключается в том, что вы направляете на блоки памяти

+----+----+----+----+----+----+ 
| 06 | 07 | 08 | 09 | 10 | 11 | mem 
+----+----+----+----+----+----+ 
| 18 | 24 | 17 | 53 | -7 | 14 | data 
+----+----+----+----+----+----+ 

Если у вас есть int* p = &(array[5]) то *p будет 14. Идя p=p-3 бы *p быть 17.

Так что если у вас есть int* p = &(array[5]) и int *q = &(array[3]), тогда p-q должно быть 2, так как указатели указывают на память, разделенную на 2 блока.

При работе с необработанной памятью (массивы, списки, карты и т. Д.) Нарисуйте много ящиков! Это действительно помогает!

+12

Кстати, если я могу подчеркнуть одно: «указатели жесткие». Да, опытные ветераны C++ могут легко взломать их в форме, но точно узнать, когда и как их использовать, - это не просто задача. – corsiKa

+9

@ glowcoder: так могут закаленные C-кодеры, что, возможно, более уместно в вопросительном вопросе C. –

+0

@Neil: FWIW, мне потребовалось много размышлений, чтобы «получить» указатели, и я чувствовал, что делаю что-то не вполне нормально, по крайней мере, несколько месяцев спустя. Это было после изучения четырех языков ассемблера, поэтому не было понимания базового механизма. Конечно, это было в Паскале, где указатели выглядели как запоздалые мысли и, как правило, не упоминались в книгах. –

9

Так что ответ будет одинаковым даже на платформах, где целые числа различной длины.

+1

Итак, как я могу воспроизвести этот вонючий факт :( Приводит меня в замешательство :( –

+0

Но когда вы выполняете добавление в указателях, например * p + 2, если его указатель int движется на 4 позиции вперед. Почему? –

+2

@fahad: если вы хотите, чтобы количество байтов между двумя адресами, вы можете отбросить два адреса к символу 'char *', а затем принять разницу. Откуда вы понимаете, что альтернатива лучше? Вы программировали на ассемблере до C? Механизм C звучит - индексирование массива зависит от него (потому что операция субтипизации добавляет N к базовому адресу, но это N единиц размера того, что находится в массиве). –

7

Скажем у вас есть массив из 10 целых чисел:

int intArray[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 

Затем вы берете указатель на INTArray:

int *p = intArray; 

Тогда вы инкремент p:

p++; 

Что вы бы ожидаем, потому что p начинается в intArray[0], является для приращенного значения p должно быть intArray[1]. Вот почему арифметика указателей работает так. See the code here.

+0

+1 Хорошая точка :) –

1

При применении арифметических операций над указателями определенного типа вы всегда хотите, чтобы результирующий указатель указывал на адрес «допустимый» (то есть размер правого шага) адрес памяти относительно исходной точки. Это очень удобный способ доступа к данным в памяти независимо от базовой архитектуры.

Если вы хотите использовать другой «размер шага» вы всегда можете привести указатель к нужному типу:

int a = 5; 
int* pointer_int = &a; 
double* pointer_double = (double*)pointer_int; /* totally useless in that case, but it works */ 
+0

«абсолютно бесполезно» правильно! «но он работает», может быть, на вашем компиляторе, сегодня ... **, но ** стандарт не требует ничего, кроме UB для разыменования указателя (не-'[unsigned] char') в памяти, реально выделенного для несвязанного типа , также, если 'double' имеет более строчное выравнивание, чем' int', тогда это может создать ловушку (или, опять же, любую другую UB). –

34

Потому что все в указатель земли о смещениях. Когда вы говорите:

int array[10]; 
array[7] = 42; 

То, что вы на самом деле говорят во второй линии:

*(&array[0] + 7) = 42; 

буквально переводится как:

* = "what's at" 
(
    & = "the address of" 
    array[0] = "the first slot in array" 
    plus 7 
) 
set that thing to 42 

И если мы можем добавить 7, чтобы сделать точка смещения в нужное место, мы должны иметь возможность иметь противоположное место, иначе у нас нет симметрии в нашей математике.Если:

&array[0] + 7 == &array[7] 

Тогда для здравомыслия и симметрии:

&array[7] - &array[0] == 7 
+4

+1 для указателя-земли – Secret

3

«Когда вы вычитать два указателя, до тех пор, как они указывают на тот же массив, результатом является количество элементов, разделяя их»

Проверить дополнительную информацию here.

+0

nice quote :) Я хочу его объяснить, P –

+2

@fahad: поскольку указатели не указывают на байты обязательно, они указывают на объекты типа, используемые при их определении. Арифметика указателя выполняется с точки зрения числа этих объектов. – JeremyP

+1

@fahad - Возможно, вы можете рассказать нам, какой размер базовых типов данных соответствует вам? int, char и float для начала. Исходя из этого, вы можете выкопать в арифметику указателя, которая отличается от обычной арифметики. (не полностью, но имеет свои правила;)) –

0

@fahad Указатель арифметики проходит по размеру типа данных, который он указывает. Поэтому, когда указатель ur имеет тип int, вы должны ожидать арифметики указателя в размере int (4 байта). Аналогично для указателя char все операции над указатель будет в 1 байт.

+1

'size of int (4 байта)' Размер 'int' зависит от платформы, он не должен быть 4 байта. Фактически в системе я использую 'int' длиной 2 байта. Проверьте эту статью, например [размер long int на разных архитектурах] (http://software.intel.com/en-us/articles/size-of-long-integer-type-on-different-architecture-and-os) –

1

Это согласуется с тем, как ведет себя указатель указателя. Это означает, что p1 + (p2 - p1) == p2 *.

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

C мог быть сконструированы таким образом, что добавлены указатели и вычитаться таким же образом, как целые числа, но это означало бы:

  • делает p2 = p1 + n * sizeof(*p1) вместо p2 = p1 + n
  • делает n = (p2 - p1)/sizeof(*p1) вместо n = p2 - p1

Это приведет к тому, что код будет длиннее, труднее для чтения и менее безопасен.

*: считая, что разница между p1 и p2 является точным кратным типа, на который они указывают. Если они находятся в одном массиве памяти, это будет верно. Если это не так, то мало смысла делать арифметику указателей на них.

Смежные вопросы