Я пытаюсь определить, вызывает ли следующий код неопределенное поведение:
#include <iostream>
class A;
void f(A& f)
{
char* x = reinterpret_cast<char*>(&f);
for (int i = 0; i < 5; ++i)
std::cout << x[i];
}
int main(int argc, char** argue)
{
A* a = reinterpret_cast<A*>(new char[5])
f(*a);
}
Я понимаю, что reinterpret_cast
с и из char*
соответствуют, потому что стандарт разрешает совмещение с char
а также unsigned char
указатели (выделение мое):
Если программа попытки доступа сохраненное значение объекта через lvalue, отличное от одного из следующих типов, поведение не определено:
- динамический тип объекта,
- cv-квалифицированная версия динамического типа объекта,
- тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
- тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или автономного объединения),
- тип, который является (возможно, квалифицированным cv) типом базового класса динамического типа объекта,
char
или жеunsigned char
тип.
Тем не менее, я не уверен, f(*a)
вызывает неопределенное поведение, создавая A&
ссылка на неверный указатель. Похоже, решающим фактором является то, что означает «попытки получить доступ» в контексте стандарта C ++.
Моя интуиция заключается в том, что это делает не составляют доступ, так как доступ потребует A
быть определенным (это объявлено, но не определено в этом примере). К сожалению, я не могу найти конкретное определение «доступа» в стандарте C ++:
Есть ли f(*a)
вызвать неопределенное поведение? Что представляет собой «доступ» в стандарте C ++?
Я понимаю, что независимо от ответа, вероятно, плохая идея полагаться на такое поведение в рабочем коде. Я задаю этот вопрос в первую очередь из-за желания улучшить мое понимание языка.
[Редактировать] @SergeyA процитировал этот раздел стандарта. Я включил это здесь для удобства (выделение мое):
5.3.1 / 1 [expr.unary.op]
Одинарный
*
Оператор выполняет косвенное обращение: выражение, к которому оно применяется, должно быть указателем на тип объекта или указателем на тип функции, а результатом является lvalue, указывающее на объект или функцию, на которые указывает выражение. Если тип выражения «указатель наT
Тип результата «T
. »[Примечание: допустимо перенаправление через указатель на неполный тип (кроме cv void). Полученное таким образом l-значение может использоваться ограниченным образом (например, для инициализации ссылки); это значение не должно быть преобразовано в предварительное значение, см. 4.1. — конец примечания]
Прослеживая ссылку на 4.1, мы находим:
4.1 / 1 [conv.lval]
Glvalue (3.10) нефункционального типа, не являющегося массивом
T
может быть преобразован в prvalue. ЕслиT
является неполным типом, программа, которая требует этого преобразования, плохо сформирована. Если T не тип класса, тип prvalue является cv-unqualified версиейT
, В противном случае тип prvalueT
,Когда преобразование lvalue-в-значение применяется к выражению
e
, и либо:
e
потенциально не оценивается, или- оценка
e
результаты в оценке членаex
из множества потенциальных результатовe
, а такжеex
называет переменнуюx
это не Odr-используетсяex
(3.2)значение, содержащееся в к указанному объекту нет доступа.
Я думаю, что наш ответ заключается в том, *a
удовлетворяет второй пункт пули. У меня проблемы с анализом этого состояния, поэтому я не уверен.
char* x = reinterpret_cast<char*>(&f);
является действительным. Или, более конкретно, доступ через x
разрешено — сам актерский состав всегда действителен
A* a = reinterpret_cast<A*>(new char[5])
не является действительным — или, если быть точным, доступ через a
вызовет неопределенное поведение.
Причиной этого является то, что в то время как это нормально, чтобы получить доступ к объекту через char*
, это не нормально для доступа к массиву символов через случайный объект. Стандарт позволяет первое, но не второе.
Или, с точки зрения непрофессионала, вы можете псевдоним type*
через char*
, но вы не можете псевдоним char*
через type*
,
РЕДАКТИРОВАТЬ
Я просто заметил, что не ответил на прямой вопрос («Что представляет собой «доступ» в стандарте C ++«). Видимо, Стандарт не определяет доступ (по крайней мере, я не смог найти формальное определение), но разыменование указателя обычно понимается как право доступа.
Других решений пока нет …