it-roy-ru.com

В чем смысл ограничений арифметики указателей или сравнения?

В C/C++ сложение или вычитание по указателю определяется только в том случае, если результирующий указатель находится в исходном указанном полном объекте . Кроме того, сравнение двух указателей может быть выполнено только в том случае, если два указанных объекта являются подобъектами уникального полного объекта.

Каковы причины таких ограничений?

Я предположил, что сегментированная модель памяти (см. здесь §1.2.1) может быть одной из причин, но так как компиляторы могут фактически определить общий порядок по всем указателям, как продемонстрировано этим ответом , я сомневаюсь в этом ,.

24
Oliv

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

Даже если вы произвольно задали ранжирование между различными адресными пространствами, существует вероятность, что тип diff_t должен быть большего размера. И процесс сравнения или вычитания двух указателей будет очень сложным. Это плохая идея на языке, который разработан для скорости.

8
Mark Ransom

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

В частности, разница должна иметь тип ptrdiff_t, и можно предположить, что он похож на size_t.

В сегментированной модели памяти у вас (обычно) есть косвенное ограничение на размеры объектов - при условии, что ответы в: Каков реальный размер типов `size_t`,` uintptr_t`, `intptr_t` и` ptrdiff_t` на 16 системы, использующие режим сегментированной адресации? Верны.

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

C был разработан, чтобы быть более минималистичным и не заставлять компилятор тратить память и код на такие случаи. (В те дни ограничения памяти имели значение больше.)

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

10
Hans Olsson

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

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

9
Johan

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

Это восходит к предстандарту C. Это обоснование не упоминает об этом явно, но намекает на то, что это является причиной, если мы посмотрим, где объясняется, почему использование отрицательного индекса массива является неопределенным поведением (C99 обоснование 5.10 6.5.6, выделено мое):

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

3
Lundin

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

  1. Могут быть платформы, где определение поведения будет дорогим. Сегментированные архитектуры могут вести себя странно, если код пытается выполнить арифметику указателей, выходящую за границы объекта, и некоторые компиляторы могут оценивать p > q, проверяя знак q-p.

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

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

И № 1, и № 2 - очень низкие бары, а № 3 считался «дай мне». Хотя авторам компиляторов стало модно демонстрировать свою хитрость, находя способы взломать код, поведение которого определялось качественными реализациями, предназначенными для низкоуровневого программирования, я не думаю, что авторы стандарта ожидали, что авторы компиляторов воспримут огромное различие между действиями, которые должны были вести себя предсказуемо, по сравнению с действиями, в которых почти все качественные реализации должны были вести себя одинаково, но там, где это возможно, было бы целесообразно позволить некоторым тайным реализациям делать что-то другое.

2
supercat

Поскольку стандарт C предназначен для охвата большинства процессорных архитектур, он также должен охватывать и следующую: Представьте себе архитектуру (я знаю одну, но не назвал бы ее), в которой указатели - это не просто числа, а структуры или «дескрипторы». Такая структура содержит информацию об объекте, на который она указывает (его виртуальный адрес и размер), и о смещении внутри него. Добавление или вычитание указателя создает новую структуру с отрегулированным только полем смещения; Аппаратно запрещено создавать структуру со смещением, превышающим размер объекта. Существуют и другие ограничения (например, способ создания исходного дескриптора или другие способы его изменения), но они не имеют отношения к теме.

2
Sergey Barannikov

Я хотел бы ответить на это, перевернув вопрос. Вместо того, чтобы спрашивать, почему добавление указателей и большинство арифметических операций недопустимы, почему указатели позволяют только добавлять или вычитать целое число, добавлять и предварительно увеличивать и уменьшать и сравнивать (или вычитать) указатели, указывающие на тот же массив? Это связано с логическим следствием арифметической операции . Добавление/вычитание целого числа n к указателю p дает мне адрес n-го элемента из текущего указанного элемента в прямом или обратном направлении. Точно так же вычитание p1 и p2, указывающих на один и тот же массив, дает мне количество элементов между двумя указателями . Факт (или дизайн), что арифметические операции с указателями определяются в соответствии с типом переменной, на которую он указывает, является настоящий гениальный ход. Любая операция, кроме разрешенных, не поддается программированию или философски логическому обоснованию и поэтому не допускается.

1
Seshadri R

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

Вопрос был Каковы причины таких ограничений (арифметика указателей и сравнение должны попадать в уникальный законченный объект)?

Комментарий и ответ Ransom: поскольку отсутствует понятие адресного пространства, единственная возможность ограничить арифметику указателей попаданием в адресное пространство заключалась в том, чтобы ограничить его на объекте.

Комментарий Крейзи Глью также дает социологически ориентированный ответ.

0
Oliv