Почему C++ запрещает создание допустимых указателей с допустимого адреса и типа?
Если вы знаете две части информации:
- Адрес памяти.
- Тип объекта хранится в этом адресе.
Тогда у вас есть все необходимое для ссылки на этот объект:
#include <iostream>
using namespace std;
int main()
{
int x = 1, y = 2;
int* p = (&x) + 1;
if ((long)&y == (long)p)
cout << "p now contains &y\n";
if (*p == y)
cout << "it also dereference to y\n";
}
Однако это не разрешено стандартом C++. Он работает в нескольких компиляторах, которые я пробовал, но это неопределенное поведение.
Вопрос в том, почему?
4 ответа
Это наносит ущерб оптимизации.
void f(int* x);
int g() {
int x = 1, y = 2;
f(&x);
return y;
}
Если вы действительно можете "угадать" адрес y
от x
адрес, затем вызов f
может изменить y
и так return
оператор должен перезагрузить значение y
из памяти.
Теперь рассмотрим типичную функцию с большим количеством локальных переменных и большим количеством обращений к другим функциям, где вам придется сохранять значение каждой переменной в памяти перед каждым вызовом (потому что вызываемая функция может их проверять) и перезагружать их после каждого вызова (потому что вызываемая функция могла изменить их).
Если вы хотите рассматривать указатели как числовой тип, сначала вам нужно использовать std::uintptr_t
не long
, Это первое неопределенное поведение, но не то, о котором вы говорите.
Он работает в нескольких компиляторах, которые я пробовал, но это неопределенное поведение.
Вопрос в том, почему?
Итак, раздел комментариев отключился, когда я вызвал это неопределенное поведение. Это на самом деле неопределенное поведение (определенное реализацией).
Вы пытаетесь сравнить два явно не связанных указателя:
&x + 1
&y
Указатель &x+1
указатель на один конец Стандарт позволяет вам иметь такой указатель, но поведение определяется только тогда, когда вы используете его для сравнения с указателями на основе x
, Поведение не указывается, если вы сравниваете его с чем-либо еще: [expr.eq § 3.1]
Компилятор свободен поставить y
где угодно, в том числе и в реестре. Таким образом, нет никакой гарантии, что &y
а также &x+1
относятся к.
В качестве упражнения для того, кто хочет показать, является ли это фактически неопределенным поведением или нет, возможно, начните здесь:
http://eel.is/c++draft/basic.stc.dynamic.safety:
Целочисленное значение является целочисленным представлением надежно полученного указателя, только если его тип по крайней мере такой же, как std:: intptr_t, и это одно из следующего:...
3.4 результат аддитивной или побитовой операции, один из операндов которой является целочисленным представлением безопасно полученного значения указателя P, если этот результат, преобразованный с помощью reinterpret_cast, сравнивается равным безопасно полученному указателю, вычисляемому из reinterpret_cast (P).
Примечание. Указатель за концом объекта ([expr.add]), как считается, не указывает на несвязанный объект типа объекта, который может находиться по этому адресу.
Если вы знаете адрес и тип объекта, и ваша реализация имеет ослабленную безопасность указателя http://eel.is/c++draft/basic.stc.dynamic.safety, тогда будет законно просто получить доступ к объекту по этому адресу через соответствующее значение l, я думаю.
Проблема в том, что стандарт не гарантирует, что локальные переменные одного и того же типа размещаются непрерывно, а адреса увеличиваются в порядке объявления. Таким образом, вы не можете получить адрес y
на основе этого вычисления вы делаете с адресом x
, Кроме того, арифметика указателей приведет к неопределенному поведению, если вы пройдете более одного элемента за объектом ( [expr.add]). Так что пока (&x) + 1
еще не определено поведение, просто акт даже вычислений (&x) + 2
было бы…
Код допустим в соответствии со стандартом C++ (т.е. должен компилироваться), но, как вы уже заметили, поведение не определено. Это связано с тем, что порядок объявления переменных не означает, что они будут расположены в памяти одинаково.