2011-02-01 3 views
17

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

(например, вычитать указатель с адресом 1 из указателя байта с адресом 0xFFFFFFFF, когда у вас есть 32- бит, и он переполняет знаковый бит ...)

ответ

28

Нет, это не так.

$ 5,7 [expr.add] (от N3225 - C++ 0x FCD)
Когда два указателя на элементы одного и того же объекта массива вычитаются, результатом является разность индексов из двух элементы массива. Тип результата - это определенный тип интегрированного интегрального типа; этот тип должен быть того же типа, что и std::ptrdiff_t в заголовке (18.2). Как и при любом другом арифметическом переполнении, если результат не соответствует указанному пространству, поведение не определено. Другими словами, если выражения P и Q указывают, соответственно, i -м и j -ый элементы объекта массива, выражение (P)-(Q) имеет значение i − j при условии, что значение помещается в объект типа std::ptrdiff_t. Более того, если выражение P указывает на элемент объекта массива или один за последним элементом объекта массива, а выражение Q указывает на последний элемент того же объекта массива, выражение ((Q)+1)-(P) имеет то же значение, что и ((Q)-(P))+1 и как -((P)-((Q)+1)), и имеет значение 0, если выражение P указывает один за последним элементом объекта массива, хотя выражение (Q)+1 не указывает на элемент объекта массива. Если оба указателя не указывают на элементы одного и того же объекта массива или один за последним элементом объекта массива, поведение не определено.

Обратите внимание, что в параграфе указано число undefined. Также обратите внимание, что вы можете только вычитать указатели, если они указывают внутри одного и того же объекта.

+0

+1 Ударьте его на несколько секунд. Хорошая рытье! – templatetypedef

+0

+1 Ничего себе, я (очевидно) никогда не знал, что эта неопределенность была частью стандарта. Но, в данном случае, это не 'ptrdiff_t' просто прославленный' signed size_t'? – Mehrdad

+2

@templatetypedef: Я рад, на этот раз, чтобы не проснуться после битвы сражался :) @Mehrdad: Я бы сказал, что это :) С другой стороны, ничто не запрещает стандартную реализацию библиотеки, которую вы используете для использования 64 -бит целых чисел даже на 32-битной платформе. Стандарт только говорит, что для них не обязательно прилагать усилия. Остальное - проблема качества реализации. –

8

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

+1

Но что, если у вас * есть * действительно большой массив (возможно, потому что вы * - это ядро)? – Mehrdad

+2

@Mehrdad: Ядро разрешено делать много не переносимых предположений. Например, код ядра Linux предполагает, что он будет скомпилирован с помощью GCC и что «long» имеет ту же ширину, что и указатель. В 32-разрядной и 64-разрядной системах разница между любыми двумя адресами будет соответствовать 64 битам (поскольку некоторые из старших бит в 64-разрядном адресе не используются). –

+0

Подождите, что ?! Они предполагают, что 'long' является размером указателя ?? o__o Почему они просто не используют 'ptrdiff_t' (что, я бы сказал, это более безопасная ставка) или вместо этого создаются пользовательские typedef? – Mehrdad

1

Это вполне приемлемо для ptrdiff_t того же размера, что и типы указателей, при условии, что семантика переполнения определена компилятором, так что любая разница все еще представляется. Нет никакой гарантии, что отрицательный ptrdiff_t означает, что второй указатель живет на более низком адресе в памяти, чем первый, или что ptrdiff_t вообще подписан.

+2

Хм ... вы уверены в том, что «или что ptrdiff_t подписан вообще»? – Mehrdad

+0

Не совсем, но у меня нет моей копии стандарта под рукой, и я бы не стал полагаться на это ни на что (например, я бы использовал 'a

+0

@Simon: Ах ладно, +1 в любом случае, хороший ответ. :) – Mehrdad

2

Чтобы добавить более четкие стандартные цитаты, ISO 9899:1999 §J.2/1 состояний:

поведение не определено в следующих случаях:

[...]

- результат вычитания двух указатели не представляются в объекте типа ptrdiff_t (6.5.6).

-1

Over/сгущенного математически четко определены для фиксированного размера целочисленной арифметики:

(1 - 0xFFFFFFFF) % (1<<32) = 
(1 + -0xFFFFFFFF) % (1<<32) = 
1 + (-0xFFFFFFFF % (1<<32)) = 2 

Это является правильный результат!

В частности, результат после over/underflow является псевдонимом правильного целого. На самом деле, каждое не представимое целое число является псевдонимом (неразличимым) с одним представляемым целым числом - счетчиком до бесконечности в целых числах фиксированного размера, и вы будете повторяться, круглые и круглые, как циферблат аналоговых часов.

N-разрядное целое представляет любое действительное целое число по модулю 2^N. В C по модулю 2^N записывается как% (1 < < 32).

Я считаю, что C гарантирует правильность математической корректности over/underflow, но только для целых чисел без знака. Предполагается, что под/переполнением никогда не будет (ради оптимизации).

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

+1

* «На практике целые числа со знаком являются двумя дополнениями, что не делает различий в добавлении или вычитании« * ... это не имеет смысла. Undefined undefined - * ничего * может случиться. Это означает, что вы не можете полагаться на что-либо конкретное. – Mehrdad

+1

Этот ответ абсолютно неверен. В большинстве случаев вычисление будет выполняться как указано, но компилятор также может делать все, что он хочет: это неопределенное поведение. [См. Это] (http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html). – Arnaud

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