Что такое действительный указатель в gcc linux x86-64 C ++?

Я программирую на C ++ с помощью gcc в малоизвестной системе под названием linux x86-64. Я надеялся, что, возможно, есть несколько человек, которые использовали ту же самую конкретную систему (и, возможно, также смогут помочь мне понять, что является действительным указателем в этой системе). Мне не нужен доступ к местоположению, на которое указывает указатель, я просто хочу вычислить его с помощью арифметики указателя.

Согласно разделу 3.9.2 стандарта:

Допустимое значение типа указателя объекта представляет либо адрес байта в памяти (1.7), либо нулевой указатель.

И согласно [expr.add] / 4:

Когда выражение, имеющее целочисленный тип, добавляется к указателю или вычитается из него, результат имеет тип операнда указателя. Если выражение P указывает на элемент x [i] объекта массива x с n элементами, выражения P + J и J + P (где J имеет значение j) указывают на (возможно, гипотетический) элемент x [i + j], если 0 ≤ i + j ≤ n; в противном случае поведение не определено. Точно так же выражение P — J указывает на (возможно, гипотетический) элемент x [i — j], если 0 ≤ i — j ≤ n; в противном случае поведение не определено.

И согласно вопросу stackoverflow о действительных указателях C ++ в целом:

Является ли 0x1 допустимым адресом памяти в вашей системе? Что ж, для некоторых встроенных систем это так. Для большинства операционных систем, использующих виртуальную память, страница, начинающаяся с нуля, зарезервирована как недействительная.

Что ж, это ясно дает понять! Итак, помимо NULL, действительный указатель — это байт в памяти, нет, подождите, это элемент массива, включающий элемент сразу после массива, нет, подождите, это страница виртуальной памяти, нет, подождите, это Супермен!

(Я предполагаю, что под «Суперменом» здесь я подразумеваю «сборщиков мусора» … не то чтобы я это где-то читал, просто почувствовал это. Если серьезно, то все лучшие сборщики мусора не ломаются серьезно, если у вас есть подделка. валяются указатели; в худшем случае они просто не собирают время от времени несколько мертвых объектов. Не похоже, что из-за чего стоит испортить арифметику указателей.).

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

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

Возьмем, например, большой массив, который я создал сам, и небольшой массив, который я позволил диспетчеру памяти по умолчанию создать внутри него:

#include <iostream> #include <inttypes.h> #include <assert.h> using namespace std; extern const char largish[1000000000000000000L]; asm(«largish = 0»); int main() { char* smallish = new char[1000000000]; cout << «largish base = » << (long)largish << «n» << «largish length = » << sizeof(largish) << «n» << «smallish base = » << (long)smallish << «n»; }

Результат:

largish base = 0 largish length = 1000000000000000000 smallish base = 23173885579280

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

Учитывая количество способов управления памятью и комбинирования программных модулей, которые поддерживаются в Linux x86-64, компилятор C ++ действительно не может знать обо всех массивах и различных стилях сопоставления страниц.

Наконец, почему я конкретно упоминаю gcc? Поскольку часто кажется, что любой указатель рассматривается как действительный указатель … Возьмем, например:

char* super_tricky_add_operation(char* a, long b) {return a + b;}

Хотя после прочтения всех спецификаций языка вы можете ожидать, что реализация super_tricky_add_operation(a, b) будет изобиловать неопределенным поведением, на самом деле это очень скучно, просто инструкция add или lea. Это так здорово, потому что я могу использовать его для очень удобных и практичных вещей, таких как ненулевые массивы, если никто не пытается с моими add инструкциями, чтобы указать на недопустимые указатели. Я люблю gcc.

Таким образом, кажется, что любой компилятор C ++, поддерживающий стандартные инструменты связывания в Linux x86-64, почти должен будет рассматривать любой указатель как действительный указатель, и gcc, похоже, является членом этого клуба. Но я не совсем уверен (то есть при достаточной дробной точности).

Итак … может ли кто-нибудь привести убедительный пример недопустимого указателя в gcc linux x86-64? Под твердым я подразумеваю, что ведет к неопределенному поведению. И объясните, что вызывает неопределенное поведение, разрешенное спецификациями языка?

(или предоставьте gcc документацию, доказывающую обратное: что все указатели действительны).

Комментарии не подлежат расширенному обсуждению; этот разговор был перемещен в чат. Если вы хотите выразить свою точку зрения, опубликуйте ответ. Если вы считаете, что вопрос не подлежит ответу в его текущем состоянии, проголосуйте за закрытие.   —  person personal_cloud    schedule 03.03.2019

@ Коди Грей Отличная идея! Я опубликовал ответ на основе данных расширенного обсуждения (недавно преобразованного в чат).   —  person personal_cloud    schedule 03.03.2019

Вы изучали возможность создания абстрактного типа данных массива, отличного от нуля?   —  person personal_cloud    schedule 03.03.2019

Вы знаете, что такое неопределенное поведение? Это не авария. Это не поджигает ваш компьютер. Это не вызов полиции, не кража твоей девушки, не начало ядерной войны. Или все это. Это просто поведение, о котором стандарт отказывается говорить, не более того. Почему вы ожидаете снова найти в super_tricky_add_operation особенно забавный ассемблерный код?   —  person personal_cloud    schedule 04.03.2019

Под твердым я подразумеваю, что ведет к неопределенному поведению. Как вы планируете определять неопределенное поведение? Глядя на свой компьютер и наблюдая сбой? Тебе этого не сделать. Посмотрев на свой компьютер и увидев, что он загорелся? Тебе этого не сделать. Не наблюдая за тем, как ваш дом подвергается нападению, не наблюдая за уходом вашей девушки, не наблюдая за концом мира в ядерном апокалипсисе. Вы можете идентифицировать UB только прочитав стандарт. Если в стандарте указано, что у вашей программы есть UB, у нее есть UB (см. Определение UB в предыдущем комментарии).   —  person personal_cloud    schedule 04.03.2019

@ n.m. Моя цель — понять, как GCC интерпретировал (расплывчатый) языковой стандарт относительно действительности указателя. Если мы сможем увидеть, как он использует языковые допущения в генерируемом ассемблерном коде, это будет очень хорошей подсказкой. Расплывчатый стандарт не означает автоматически, что GCC что-то не поддерживает.   —  person personal_cloud    schedule 04.03.2019

В действительности указателя нет ничего неопределенного. [basic.compound] Каждое значение типа указателя является одним из следующих: (3.1) — указатель на объект или функцию (считается, что указатель указывает на объект или функцию) или (3.2) — a указатель за концом объекта (8.7), или (3.3) — значение нулевого указателя (7.11) для этого типа, или (3.4) — недопустимое значение указателя. Компилятору не нужно интерпретировать это есть какой-то особенный способ. Он может предполагать, что все указатели, с которыми вы что-либо делаете, действительны.   —  person personal_cloud    schedule 04.03.2019

@ n.m. В ПОРЯДКЕ. Но разве мы не установили, что существует множество способов создать объект? И C ++ не предоставляет единую конструкцию или интерфейс фасада для обнаружения всех этих различных типов объектов (кроме попыток доступа к ним), а только общий диапазон адресного пространства. Если я создаю новый распределитель объектов, обязан ли я каким-то образом сообщить об этом языку?   —  person personal_cloud    schedule 04.03.2019

Нет, нет. Вы можете объявить и определить объект или создать его с помощью оператора new. Таким образом, давайте посчитаем их на большом пальце, раз, два, это два способа создания объектов. Вы не открываете объекты. Вы знаете, где они. В целом у меня такое впечатление, что вы не понимаете, о чем спрашиваете. Это о симптомах УБ? Это о создании объектов? Это о действительности указателя? Это слишком широко. Пожалуйста, задавайте вопросы за раз.   —  person personal_cloud    schedule 04.03.2019

@ n.m. А как насчет mmap, malloc, ввода-вывода, общих страниц, захваченных страниц и т. Д. Это все допустимые массивы! Нет, я не знаю, откуда все это в простом API, и компилятор тоже. Да, у меня вопрос о симптомах УБ. Как объясняется в ответах, GCC действительно знает общий диапазон виртуального адресного пространства и использует его при оптимизации сравнения. Так УБ проявляется на практике. (Или всего UB можно избежать, используя uintptr_t, хотя тогда вам нужно настроить его, кратно sizeof(elem), и вернуть его к указателю перед доступом к назначенной памяти)   —  person personal_cloud    schedule 04.03.2019

Все это действительные массивы! Кто говорит? Только стандарт определяет, какой указатель является допустимым, а какой — нет. Вы можете процитировать соответствующий стандартный язык? Существует отчет о дефектах, который показывает доступ к памяти malloc’d без размещения в ней нового объекта (обычная идиома, которая исходит от C) — UB. Это прискорбно, но это то, что в настоящее время говорится в стандарте.   —  person personal_cloud    schedule 04.03.2019

@ n.m. Размещение new не является обязательным для типов C, таких как int, поскольку C ++ обратно совместим с C. Я предполагаю, что это включает в себя mmap, malloc, ввод-вывод, общие страницы, захваченные страницы и т. Д. Я не понимаю, как размещение new будет работать с ними вещи, когда другой процесс / библиотека и т. д. создавали данные. И даже для размещения new, я не думаю, что компилятору разрешено создавать для него внешнюю структуру отслеживания (где для этого ресурсы памяти?). Размещение new должно просто вызывать конструктор класса, который обычно только обновляет значения в самом классе и, возможно, выделяет некоторые члены.   —  person personal_cloud    schedule 04.03.2019

В любом случае, если вы предполагаете, что malloc создает допустимый массив символов, это еще один способ создания объекта. В C ++ нет mmap или другого способа выделения памяти. Если указатель исходит от функции, которая неизвестна реализации, например, написанной на другом языке, реализация должна предполагать, что указатель действителен, иначе было бы довольно сложно взаимодействовать с другими языками. Но тогда вы создаете объекты вне программы на C ++. Описание того, как это делается, не входит в сферу применения стандарта C ++.   —  person personal_cloud    schedule 04.03.2019

Размещение new необязательно для типов C, таких как int. Нет, это не так, поскольку C ++ обратно совместим с C. Нет, это не так.   —  person personal_cloud    schedule 04.03.2019

Реализация в значительной степени позволяет отслеживать все объекты. При взаимодействии с другим языком вам нужно будет сообщить реализации, где находятся объекты, созданные сторонними объектами, некоторым способом, зависящим от реализации. gcc не отслеживает объекты, это не такая реализация. Предполагается, что указатели, о которых он не знает, действительны. Вы обязаны никогда не делать ничего смешного с недействительными указателями.   —  person personal_cloud    schedule 04.03.2019

Источник: ledsshop.ru

Стиль жизни - Здоровье!