В языке C возможно ли семантически создать lvalue с неполным типом?

В стандарте C89 я нашел следующий раздел:

3.2.2.1 Lvalues ​​и обозначения функций

За исключением случаев, когда это операнд оператора sizeof, унарный и оператор, оператор ++, оператор-оператор или левый операнд. оператора или оператора присваивания, значение l, которое не имеет типа массива, преобразуется в значение, хранящееся в указанном объекте (и больше не является lvalue). Если lvalue имеет квалифицированный тип, значение имеет неквалифицированную версию типа lvalue; в противном случае значение имеет тип lvalue. Если lvalue имеет неполный тип и не имеет типа массива, поведение undefined.

Если я прочитал его правильно, он позволяет нам создать lvalue и применять к нему некоторые операторы, которые компилируются и могут вызывать поведение undefined во время выполнения.

Проблема в том, что я не могу придумать пример "lvalue с неполным типом", который может передавать семантическую проверку компилятора и триггеры undefined behavior.

Считайте, что lvalue

lvalue - это выражение (с типом объекта или неполным типом, отличным от void), который обозначает объект.

и что неполный тип

Типы разделяются на типы объектов (типы, описывающие объекты), типы функций (типы, описывающие функции) и неполные типы (типы, которые описывают объекты, но не имеют информации, необходимой для определения их размеров).

Ошибка программы, которую я пробовал:

struct i_am_incomplete;
int main(void)
{
    struct i_am_incomplete *p;
    *(p + 1);
    return 0;
}

и получил следующую ошибку:

error: arithmetic on a pointer to an incomplete type 'struct i_am_incomplete'
    *(p + 1);
      ~ ^

Кто-нибудь может подумать о примере? Пример "lvalue с неполным типом", который может передавать семантическую проверку компилятора и триггеры undefined behavior.


UPDATE:

Как сказал @algrid в ответе, я неправильно понял undefined behavior, который содержит compile error в качестве опции.

Возможно, я раскалываю волосы, я все еще удивляюсь, что основная мотивация здесь предпочтительнее undefined behavior over disallowing an lvalue to have an incomplete type.

Ответ 1

Некоторые системы сборки, возможно, были спроектированы таким образом, чтобы код был таким:

extern struct foo x;
extern use_foo(struct foo x); // Pass by value

...
use_foo(x);

для успешной обработки без компилятора, который должен знать или ухаживать о фактическом представлении struct foo [например, некоторые системы могут обрабатывать пропуски по значению, если вызывающий абонент передает адрес объекта и требует, чтобы вызываемая функция создавала копию, если она собирается изменить его].

Такой объект может быть полезен для систем, которые могли бы его поддерживать, и я не думаю, что авторы Стандарта хотели подразумевать, что код, который использовал эту функцию, был "сломан", но они также не хотели выполнять мандат что все реализации C поддерживают такую ​​функцию. Выполнение поведения undefined позволило бы реализациям поддерживать его, когда это было бы практично, не требуя, чтобы они это делали.

Ответ 2

Я считаю, что эта программа демонстрирует случай:

struct S;
struct S *s, *f();

int main(void)
{
    s = f();
    if ( 0 )
        *s;   // here
}

struct S { int x; };
struct S *f() { static struct S y; return &y; }

В отмеченной строке *s является lvalue неполного типа и не попадает ни в один из случаев "Except..." в вашу цитату 3.2.2.1 (это 6.3.2.1/2 в текущий стандарт). Поэтому это поведение undefined.

Я пробовал свою программу в gcc и clang, и они оба отклонили ее с ошибкой, что указатель на неполный тип не может быть разыменован; но я не могу найти нигде в стандарте, который сделал бы это нарушение ограничения, поэтому я считаю, что компиляторы неверны, чтобы отклонить программу. Или, возможно, стандарт неисправен, опуская такое ограничение, что имеет смысл.

(Так как код внутри if(0), это означает, что компилятор не может отклонить его только на основе его поведения undefined).

Ответ 3

"Undefined поведение" включает в себя ошибку компиляции в качестве опции. Из стандарта C89:

Undefined поведение - поведение, при использовании непереносимой или ошибочной программной конструкции, ошибочных данных или неопределенно значимых объектов, для которых Стандарт не налагает никаких требований. Допустимое поведение undefined варьируется от полного игнорирования ситуации с непредсказуемыми результатами, ведения во время перевода или выполнения программы документированным образом, характерным для среды (с выдачей диагностического сообщения или без него), до прекращения перевода или выполнения (с выпуск диагностического сообщения).

Как вы видите, "завершение перевода" - это нормально.

В этом случае я считаю, что ошибка компиляции, которую вы получаете для примера, является примером поведения Undefined, реализованного как ошибка времени компиляции.

Ответ 4

Конечно, типы массивов могут быть такими:

extern double A[];
...
A[0] = 1;           // lvalue conversion of A

Это имеет четко определенное поведение, даже если определение A не отображается компилятору. Поэтому внутри этого TU тип массива никогда не завершается.