Я программирую на 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
, действительный указатель - это байт в памяти, нет, подождите, это элемент массива, включающий элемент сразу после массива, нет, подождите, это страница виртуальной памяти, нет, подождите, это Супермен!
(Я полагаю, что здесь под «Суперменом» я подразумеваю «сборщиков мусора» ... не то, чтобы я где-то читал, просто пахло этим. Серьезно, хотя все лучшие сборщики мусора не ломаются в серьезномКстати, если вокруг лежат фальшивые указатели, в худшем случае они просто не собирают несколько мертвых объектов время от времени. Не похоже, чтобы что-то стоило испортить арифметику указателей.).
Итак, в основномправильный компилятор должен поддерживать всех из перечисленных выше вариантов допустимых указателей.Я имею в виду, что гипотетический компилятор, обладающий смелостью генерировать неопределенное поведение только потому, что указатель вычисление плох, уклонился бы, по крайней мере, от 3 пуль выше, верно?(Хорошо, юристы по языку, это ваше).
Более того, многие из этих определений практически невозможны для компилятора.Есть только , поэтому много способов создания действительного байта памяти (например, ленивый микрокод ловушки сегфо, подсказки боковой полосы для пользовательской системы с табличными таблицами, к которой я собираюсь получить доступ к части массива, ...), отображениестраницу, или просто создание массива.
Возьмем, к примеру, большой массив, который я создал сам, и небольшой массив, который я позволил менеджеру памяти по умолчанию создать внутри него:
#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
представляется членом этого клуба.Но я не совсем уверен на 100% (учитывая достаточную дробную точность).
Так что ... может ли кто-нибудь привести убедительный пример недопустимого указателя в gcc linux x86-64?Под твердым я подразумеваю ведение к неопределенному поведению.И объясните, что приводит к неопределенному поведению, допускаемому языковыми спецификациями?
(или предоставьте gcc
документацию, доказывающую обратное: все указатели действительны).