Создание недопустимой ссылки с помощью переинтерпретации приведения

Я пытаюсь определить, вызывает ли следующий код неопределенное поведение:

#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_casts до и от char* совместимы, потому что стандарт разрешает наложение с указателями char и unsigned char (выделено мной):

Если программа пытается получить доступ к сохраненному значению объекта через lvalue, отличное от одного из следующих типов, поведение не определено:

  • динамический тип объекта,
  • cv-квалифицированная версия динамического типа объекта,
  • тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
  • тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
  • тип агрегата или объединения, который включает в себя один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или содержащегося объединения),
  • тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
  • типа char или unsigned char.

Однако я не уверен, вызывает ли f(*a) неопределенное поведение, создавая A& ссылку на недопустимый указатель. Решающим фактором, по-видимому, является то, что означает попытки использовать словоблудие в контексте стандарта C ++.

Моя интуиция такова, что это не составляет доступ, поскольку для доступа потребуется определить A (он объявлен, но не определен в этом примере). К сожалению, я не могу найти конкретное определение доступа в стандарте C ++:

f(*a) вызывает неопределенное поведение? Что представляет собой доступ в стандарте C ++?

Я понимаю, что, независимо от ответа, полагаться на такое поведение в производственном коде - плохая идея. Я задаю этот вопрос в первую очередь из-за желания улучшить свое понимание языка.

[Edit] @SergeyA процитировал этот раздел стандарта. Я включил его сюда для удобства (выделено мной):

5.3.1 / 1 [expr.unary.op]

Унарный оператор * выполняет косвенное обращение: выражение, к которому он применяется, должно быть указателем на тип объекта или указателем на тип функции, а результатом является lvalue, относящееся к объекту или функции, на которые указывает выражение. Если тип выражения - «указатель на T», тип результата - «T». [Примечание: косвенное обращение через указатель на неполный тип (кроме cv void) допустимо. Полученное таким образом lvalue можно использовать ограниченными способами (например, для инициализации ссылки); это lvalue нельзя преобразовывать в prvalue, см. 4.1. - конец примечания]

Прослеживая ссылку на 4.1, мы находим:

4,1 / 1 [conv.lval]

Значение glvalue (3.10) нефункционального типа T, не являющегося массивом, может быть преобразовано в prvalue. Если T - неполный тип, программа, которая требует этого преобразования, плохо сформирована. Если T - неклассовый тип, тип prvalue - это cv-неквалифицированная версия T. В противном случае тип prvalue - T.

Когда преобразование lvalue-to-rvalue применяется к выражению e, либо:

  • e потенциально не оценивается, или
  • оценка e приводит к оценке члена ex множества потенциальных результатов e, а ex называет переменную x, которая не используется ex (3.2)

значение, содержащееся в указанном объекте, недоступно.

Я думаю, что наш ответ заключается в том, удовлетворяет ли *a второму пункту. У меня проблемы с анализом этого условия, поэтому я не уверен.


person Michael Koval    schedule 06.04.2016    source источник
comment
Каков расклад A?   -  person curiousguy    schedule 07.04.2016


Ответы (1)


char* x = reinterpret_cast<char*>(&f); действительно. Или, точнее, разрешен доступ через x - само приведение всегда верно.

A* a = reinterpret_cast<A*>(new char[5]) недействителен - или, если быть точным, доступ через a вызовет неопределенное поведение.

Причина этого в том, что, хотя доступ к объекту через char* можно получить, нельзя обращаться к массиву символов через случайный объект. Стандарт допускает первое, но не второе.

Или, говоря непрофессиональным языком, вы можете использовать псевдонимы с type* по char*, но не можете использовать псевдонимы с char* по type*.

ИЗМЕНИТЬ

Я только заметил, что не ответил на прямой вопрос («Что представляет собой« доступ »в стандарте C ++»). Очевидно, Standard не определяет доступ (по крайней мере, мне не удалось найти формальное определение), но обычно считается, что разыменование указателя дает право на доступ.

person SergeyA    schedule 06.04.2016
comment
Я согласен с тем, что доступ через a вызовет неопределенное поведение, но (как вы упомянули) A* a = reinterpret_cast<A*>(new char[5]) не может представлять собой доступ. Опять же, ответ сводится к тому, что представляет собой доступ. Является ли привязка указателя или преобразование в lvalue (возможно, я ошибаюсь в типе) доступ? - person Michael Koval; 06.04.2016
comment
A* a = ... - это не доступ. Но разыменование указателя - это доступ. - person SergeyA; 06.04.2016
comment
Извините за педантизм, но почему бы и нет? Мне непонятно, почему A& b = *a по своей сути больше доступа, чем A* b = a. Ни одно из этих утверждений не требует определения A; только заявлено. Есть ли что-нибудь в стандарте, намекающее на такую ​​интерпретацию? - person Michael Koval; 06.04.2016
comment
Да, и я сказал это несколько раз - разыменование указателя. Конкретным стандартным термином является косвенное обращение, и, как указано в п. 5.3.1 / 1, для инициализации ссылки разрешено косвенное обращение к неполному типу. Тем не менее, это все еще понимается как доступ. - person SergeyA; 06.04.2016
comment
Спасибо за указатель на 5.3.1 / 1 - это определенно актуально. Однако я специально ищу утверждение в стандарте, в котором говорится, что разыменование указателя (косвенное обращение) составляет доступ. Я следил за ссылкой на 4.1 / 1, которая определяет, когда преобразование lvalue в prvalue представляет собой доступ. Я не уверен, но я думаю, что A& b = *a не является доступом, потому что результат не используется odr. - person Michael Koval; 06.04.2016
comment
@MichaelKoval, ну, насколько мне известно, стандарт не имеет формального определения доступа. Однако, похоже, в этом и состоит суть вашего вопроса. В этом случае я предлагаю вам задать другой простой вопрос, свободный от псевдонимов типов или чего-либо еще - что составляет доступ. Приведите этот пример (хотя и со ссылкой на тот же тип). Вероятно, вы получите более интересные ответы, чем здесь. - person SergeyA; 06.04.2016
comment
Справедливо. Я поставлю еще один вопрос конкретно об определении доступа в стандарте C ++. - person Michael Koval; 06.04.2016