Составной литерал создается один раз для данной области

#c #language-lawyer #undefined-behavior #compound-literals

Вопрос:

Я довольно запутался в том, N2346::6.5.2.5/15 и N2346::6.5.2.5/16 какие состояния (например, мои)

15 ПРИМЕР 8 Каждый составной литерал создает только один объект в заданной области

 struct s { int i; };
int f (void)
{
    struct s *p = 0, *q;
    int j = 0;
    again:
        q = p, p = amp;((struct s){ j   });
        if (j < 2) goto again;
    return p == q amp;amp; q->i == 1;
}
 

Функция f() всегда возвращает значение 1.

16 Обратите внимание, что если бы оператор итерации использовался вместо явного goto и помеченного оператора, время жизни неназванного объекта было бы только телом цикла, и при вводе в следующий раз
p имело бы неопределенное значение, что привело бы к
неопределенному поведению.

Цитата показалась мне противоречащей другой части Стандарта. Точно:

N2346::6.5.2.5/5

Если составной литерал встречается вне тела функции, объект имеет статическую продолжительность хранения; в противном случае он имеет автоматическую продолжительность хранения, связанную с заключающим блоком.

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

N2346::6.8/3 (эмп. мой):

Инициализаторы объектов, которые имеют автоматическую продолжительность хранения, и переменной длины массива деклараторы простых идентификаторов с блоком сферы, оцениваются и значения хранятся в объектес (в том числе хранение неопределенное значение в объектах без инициализатора) каждый раз, когда декларации достигается в порядке
выполнения, как если бы это было утверждение, а в каждой декларации в порядке, которые появляются деклараторы.

Таким образом, даже если goto оператор в примере N2346::6.5.2.5/15 заменен оператором итерации, объект, созданный составным литералом, должен создаваться заново каждый раз при его достижении.

ВОПРОС: Почему замена goto оператором итерации приводит к UB? Что не так с моими рассуждениями?

Комментарии:

1. Просто — указатель ссылается на объект, которого больше не существует. Это то же самое, что возвращать указатель на автоматический объект при выходе из функции

2. Интересно, рассматривали ли авторы Стандарта угловые случаи goto , когда они писали правила жизни для составных литералов? Заявленное обоснование того, что их срок службы не продлевается с помощью функции, состояло в том, что это означало бы, что каждая итерация цикла создаст новый объект, но ограничение срока службы областью действия блока никак не помогает избежать этого углового случая. Как бы то ни было, ограничение срока службы областью действия блока без необходимости снижает полезность составных литералов, а спецификация, в которой создается только один объект, запрещает оптимизацию с развертыванием цикла…

3. …разные объекты для каждой итерации цикла, который может иногда быть очень полезно с const-квалифицированный литералы (например, если внутренний цикл выполняет итерацию i от 0 до 4, и использует соединение литерала (const S){1,2,3,i}; компилятор мог бы создать статический пять-продолжительность объекты, по одному для каждого значения i , и у каждой раскатала итерации внутреннего цикла проходят в адрес одного такого объекта.

Ответ №1:

даже если оператор goto в примере N2346::6.5.2.5/15 заменен оператором итерации, объект, созданный составным литералом, должен создаваться заново каждый раз при его достижении.

Вы правы, но важным моментом является то, что конец блока сигнализирует об окончании срока хранения объекта. Неопределенное поведение запускается q = p во второй итерации, когда p оно больше не является допустимым, а также в return строке за пределами оператора итерации.


Более конкретно, стандарт ссылается на такой код:

 struct s { int i; };
int f (void)
{
    struct s *p = 0, *q;
    int j = 0;
    for (j = 0; j < 2; j  )
    {
        q = p; // p is invalid in the second iteration
        p = amp;((struct s){ j   });
    } // end of block - p is no longer valid!

    // p points to an object whose storage duration has expired, and so this is undefined behavior
    return p == q amp;amp; q->i == 1;
}
 

вы можете увидеть заключительную return инструкцию, ссылающуюся на объект, срок хранения которого истек в конце for блока, и q переменную, назначенную указателю, который не определен во второй итерации.

Определяющее различие между использованием goto и оператором итерации, таким как for цикл, заключается в том, что объекты, созданные внутри for цикла, действительны только внутри области цикла.

Комментарии:

1. Спасибо, не уловил, что означал Стандарт.