Объединенное литеральное время жизни и блоки
Это теоретический вопрос, я знаю, как сделать это однозначно, но мне стало любопытно и выкопано в стандарте, и мне нужна вторая пара стандартов адвокатов.
Начнем с двух структур и одной функции init:
struct foo {
int a;
};
struct bar {
struct foo *f;
};
struct bar *
init_bar(struct foo *f)
{
struct bar *b = malloc(sizeof *b);
if (!b)
return NULL;
b->f = f;
return b;
}
Теперь у нас есть неаккуратный программист, который не проверяет возвращаемые значения:
void
x(void)
{
struct bar *b;
b = init_bar(&((struct foo){ .a = 42 }));
b->f->a++;
free(b);
}
Из моего чтения стандарта нет ничего неправильного здесь, кроме потенциального разыменования указателя NULL. Изменение struct foo
через указатель в struct bar
должно быть законным, поскольку время жизни составного литерала, отправленного в init_bar
, является блоком, в котором он содержался, что является всей функцией x
.
Но теперь у нас есть более осторожный программист:
void
y(void)
{
struct bar *b;
if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL)
err(1, "couldn't allocate b");
b->f->a++;
free(b);
}
Код делает то же самое, не так ли? Поэтому он тоже должен работать. Но более тщательное чтение стандарта C11 приводит меня к мысли, что это приводит к поведению undefined. (акцент в кавычки мой)
6.5.2.5 Составные литералы
5 Значение составного литерала - это имя неназванного объекта, инициализированного список инициализаторов. Если составной литерал происходит вне тела функции, объект имеет статическую продолжительность хранения; в противном случае он имеет автоматическую продолжительность хранения, связанную с закрывающий блок.
6.8.4 Операторы выбора
3 Операция выбора - это блок, объем которого является строгим подмножеством сферы его охватывающий блок. Каждое связанное с ним субстрат также является блоком, масштаб которого является строгим подмножество области действия оператора выбора.
Я читаю это правильно? Означает ли факт, что if
является блоком, означает, что время жизни составного литерала является только выражением if?
(В случае, если кто-то задается вопросом о том, откуда пришел этот надуманный пример, в реальном коде init_bar
на самом деле pthread_create
, и поток соединяется до возвращения функции, но я не хотел мутить воды,).
Ответы
Ответ 1
Вторая часть приведенного вами стандарта (6.8.4. Выражения выбора) говорит об этом. В коде:
{//scope 1
if( ... )//scope 2
{
}//end scope 2
}//end scope 1
Область 2 целиком находится внутри области 1. Обратите внимание, что в этом случае выражение выбора является целым оператором if, а не только скобками:
if( ... ){ ... }
Все, что определено в этом утверждении, находится в области 2. Поэтому, как показано в третьем примере, время жизни составного литерала, объявленного в области видимости 2, заканчивается на закрытии, если скобка (конечная область 2), так что пример приведет к поведению undefined, если функция возвращает не-NULL (или NULL, если err() не завершает программу).
(Обратите внимание, что я использовал скобки в инструкции if, хотя третий пример их не использует. Эта часть примера эквивалентна этому (6.8.2 Compound statement):
if ((b = init_bar(&((struct foo){ .a = 42 }))) == NULL)
{
err(1, "couldn't allocate b");
}