Подтвердить что ты не робот

Гибкие элементы массива могут привести к поведению undefined?

  • Используя гибкие члены массива (FAM) внутри типов структуры, показываем ли мы нашим программам возможность поведения undefined?

  • Возможно ли, чтобы программа использовала FAM и все еще была строго соответствующей программе?

  • Требуется ли смещение элемента гибкого массива в конце структуры?

Вопросы относятся как к C99 (TC3), так и к C11 (TC1).

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    printf("sizeof *s: %zu\n", sizeof *s);
    printf("offsetof(struct s, array): %zu\n", offsetof(struct s, array));

    s->array[0] = 0;
    s->len = 1;

    printf("%d\n", s->array[0]);

    free(s);
    return 0;
}

Вывод:

sizeof *s: 16
offsetof(struct s, array): 12
0
4b9b3361

Ответ 1

Короткий ответ

  • Да. Общие соглашения об использовании FAM раскрывают наши программы в возможности поведения undefined. Сказав это, я не знаю какой-либо существующей соответствующей реализации, которая будет плохо себя вести.

  • Возможно, но маловероятно. Даже если мы фактически не достигли поведения undefined, мы все еще, вероятно, потерпим строгое соответствие.

  • Нет. Смещение FAM не обязательно должно быть в конце структуры, оно может накладывать любые завершающие байты заполнения.

Ответы применяются как к C99 (TC3), так и к C11 (TC1).


Длительный ответ

FAM впервые были введены в C99 (TC0) (декабрь 1999 г.), и их первоначальная спецификация требовала, чтобы смещение FAM находилось в конце структуры. Первоначальная спецификация была четко определена и, как таковая, не могла привести к поведению undefined или быть проблемой в отношении строгого соответствия.

C99 (TC0) §6.7.2.1 p16 (декабрь 1999 года)

[Настоящий документ является официальным стандартом, он защищен авторским правом и не доступен свободно)

Проблема заключалась в том, что обычные реализации C99, такие как GCC, не соответствовали требованию стандарта и позволяли FAM накладывать любые завершающие байты заполнения. Их подход считался более эффективным, и поскольку для того, чтобы они соответствовали требованию стандарта, это привело бы к нарушению обратной совместимости, комитет решил изменить спецификацию, а с C99 TC2 (ноябрь 2004 г.) стандарт больше не требуется смещение FAM должно быть в конце структуры.

C99 (TC2) §6.7.2.1 p16 (ноябрь 2004 г.)

[...] размер структуры такой, как если бы элемент гибкого массива был пропущен, за исключением того, что он мог иметь более длинное дополнение, чем подразумевалось бы упущение.

Новая спецификация удалила оператор, который потребовал, чтобы смещение FAM было в конце структуры, и оно ввело очень неудачное следствие, потому что стандарт дает реализацию свободу не сохранять значения любых байтов заполнения внутри структур или союзов в согласованном состоянии. Более конкретно:

C99 (TC3) §6.2.6.1 p6

Когда значение хранится в объекте структуры или типа объединения, в том числе в объекте-члене, байты представления объекта, которые соответствуют любым байтам заполнения, принимают неопределенные значения.

Это означает, что если какой-либо из наших элементов FAM соответствует (или перекрывает) любые конечные байты заполнения, после хранения члену struct-they (может) принимать неуказанные значения. Нам даже не нужно обдумывать, распространяется ли это на значение, хранящееся в самой FAM, даже строгая интерпретация, которая применяется только к членам, отличным от FAM, достаточно повреждает.

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    if (sizeof *s > offsetof(struct s, array)) {
        s->array[0] = 123;
        s->len = 1; /* any padding bytes take unspecified values */

        printf("%d\n", s->array[0]); /* indeterminate value */
    }

    free(s);
    return 0;
}

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

Undefined поведение

Хотя значения байтов заполнения - это "неуказанные значения", то же самое нельзя сказать о том, какой тип ими подвергается, поскольку представление объекта, которое основано на неопределенных значениях, может генерировать представление ловушки. Таким образом, единственным стандартным термином, который описывает эти две возможности, будет "неопределенное значение". Если тип FAM имеет ловушечные представления, доступ к нему - это не просто проблема неопределенного значения, но undefined.

Но подождите, там еще. Если мы согласны с тем, что единственным стандартным термином для описания такого значения является "неопределенное значение", то даже если тип FAM не имеет ловушечных представлений, мы достигли поведения undefined, поскольку официальная интерпретация Комитет стандартов C состоит в том, что передача неопределенных значений в стандартные библиотечные функции - это поведение undefined.

Ответ 2

Это длинный ответ, в котором широко обсуждается сложная тема.

TL; DR

Я не согласен с анализом Dror K.

Ключевой проблемой является непонимание того, что означает §6.2.1 ¶6 в стандартах C99 и C11, и ненадлежащим образом применяя его к простому целочисленному присваиванию, например:

fam_ptr->nonfam_member = 23;

Это присваивание не позволяет изменять любые байты заполнения в структуре, на которую указывает fam_ptr. Следовательно, анализ, основанный на предположении, что это может изменить байты заполнения в структуре, является ошибочным.

Фон

В принципе, я не ужасно обеспокоен стандартом C99 и его исправления; они не являются текущим стандартом. Однако эволюция спецификации элементов гибкого массива познавательно.

В стандарте C99 - ISO/IEC 9899: 1999 - было три технических исправления:

  • TC1 выпущен в 2001-09-01 (7 страниц),
  • TC2 выпущен в 2004-11-15 (15 страниц),
  • TC3 выпущен в 2007-11-15 (10 страниц).

Например, TC3 утверждал, что gets() устарел и устаревший, что приводит к его удалению из стандарта C11.

Стандарт C11 - ISO/IEC 9899: 2011 - имеет один технический исправление, но это просто устанавливает значение двух макросов случайно слева в форме 201ymmL - значения, необходимые для __STDC_VERSION__ и __STDC_LIB_EXT1__ были скорректированы на значение 201112L. (Вы можете видеть TC1 - формально "ISO/IEC 9899: 2011/Cor.1: 2012 (ru) Информационные технологии - Языки программирования - C ТЕХНИЧЕСКИЕ ИСПРАВЛЕНИЕ 1 "- при https://www.iso.org/obp/ui/#iso:std:iso-iec:9899:ed-3:v1:cor:1:v1:en. Я не разработал, как вы его загрузите, но это так просто что это действительно не имеет большого значения.

Стандарт C99 для гибких элементов массива

ISO/IEC 9899: 1999 (до TC2) §6.7.2.1 ¶16:

В качестве частного случая последний элемент структуры с более чем одним именованный элемент может иметь неполный тип массива; это называется гибкий элемент массива. С двумя исключениями элемент гибкого массива игнорируется. Во-первых, размер структуры должен быть равен смещению последний элемент другой идентичной структуры, который заменяет гибкий элемент массива с массивом неуказанной длины. 106)Во-вторых, когда оператор . (или ->) имеет левый операнд, который равен (a указатель на) структуру с гибким элементом массива и правым операнд называет этот член, он ведет себя так, как если бы этот член был заменен с самым длинным массивом (с тем же типом элемента), который не структура больше, чем доступ к объекту; смещение массив должен оставаться массивом элемента гибкого массива, даже если это будет отличаются от матрицы замены. Если этот массив не будет содержать никаких элементов, он будет вести себя так, как если бы он имел один, но поведение undefined, если любая попытка сделать доступ этот элемент или сгенерировать указатель за ним.

126) Длина не указана, чтобы учесть тот факт, что реализации могут дать членам массива разные выравнивания в соответствии с к их длине.

(Эта сноска удаляется в переписывании.) Оригинальный стандарт C99 включал один пример:

¶17 ПРИМЕР Предположим, что все элементы массива выровнены одинаково, после объявлений:

struct s { int n; double d[]; };
struct ss { int n; double d[1]; };

три выражения:

sizeof (struct s)
offsetof(struct s, d)
offsetof(struct ss, d)

имеют одинаковое значение. Структура struct s имеет гибкий элемент массива d.

¶18 Если sizeof (double) равно 8, то после выполнения следующего кода:

struct s *s1;
struct s *s2;
s1 = malloc(sizeof (struct s) + 64);
s2 = malloc(sizeof (struct s) + 46);

и при условии, что вызовы malloc преуспевают, объекты, на которые указывают s1 и s2, ведут себя так, как если бы идентификаторы были объявлены как:

struct { int n; double d[8]; } *s1;
struct { int n; double d[5]; } *s2;

¶19 Следуя дальнейшим успешным назначениям:

s1 = malloc(sizeof (struct s) + 10);
s2 = malloc(sizeof (struct s) + 6);

они тогда ведут себя так, как если бы объявления были:

struct { int n; double d[1]; } *s1, *s2;

и

double *dp;
dp = &(s1->d[0]); // valid
*dp = 42;         // valid
dp = &(s2->d[0]); // valid
*dp = 42;         // undefined behavior

¶20 Назначение:

*s1 = *s2;

копирует только элемент n, а не какой-либо из элементов массива. Аналогично:

struct s t1 = { 0 };          // valid
struct s t2 = { 2 };          // valid
struct ss tt = { 1, { 4.2 }}; // valid
struct s t3 = { 1, { 4.2 }};  // invalid: there is nothing for the 4.2 to initialize
t1.n = 4;                     // valid
t1.d[0] = 4.2;                // undefined behavior

Некоторые из этого примера материала были удалены в C11. Это изменение не было отмечено (и не нужно было замечать) в TC2, поскольку примеры не являются нормативными. Но переписанный материал в C11 информативен при изучении.

Документ N983, идентифицирующий проблему с гибкими элементами массива

N983из РГ14 Pre-Santa Cruz-2002 рассылка является, я считаю, первоначальной формулировкой отчета о дефектах. В нем говорится, что некоторым компиляторам C (цитируя три) удается поставить FAM перед заполнением в конце структуры. Итоговый отчет о дефекте был DR 282.

Как я понимаю, этот отчет привел к изменению TC2, хотя Я не проследил все этапы процесса. Похоже, что DR больше не доступен отдельно.

TC2 использовал формулировку, содержащуюся в стандарте C11, в нормативном материале.

Стандарт C11 для элементов гибкого массива

Итак, что должен сказать стандарт C11 о гибких элементах массива?

§6.7.2.1 Спецификации структуры и союза

¶3 Структура или объединение не должны содержать члена с неполным или (следовательно, структура не должна содержать экземпляр сам, но может содержать указатель на экземпляр самого себя), кроме что последний элемент структуры с несколькими именами может иметь неполный тип массива; такая структура (и любой союз содержащий, возможно, рекурсивно, элемент, который является такой структурой) не должен быть членом структуры или элемента массива.

Это прочно позиционирует FAM в конце структуры - "последний член "по определению в конце структуры, и это подтверждено:

¶15 Внутри объекта структуры члены, не являющиеся битовыми полями, и единицы, в которых находятся битовые поля, имеют адреса, которые увеличиваются в порядок, в котором они объявлены.

¶17 В конце структуры или объединения может быть неназванное заполнение.

¶18 В качестве частного случая последний элемент структуры с более чем один именованный элемент может иметь неполный тип массива; это называется гибкий элемент массива. В большинстве ситуаций гибкий элемент массива игнорируется. В частности, размер структуры такой, как если бы гибкая матрица член был исключен, за исключением того, что он может иметь более длинный отступ, чем упущение будет означать. Однако, когда оператор . (или ->) имеет левый операнд, который равен (a указатель на) структуру с гибким элементом массива и правым операнд называет этот член, он ведет себя так, как если бы этот член был заменен с самым длинным массивом (с тем же типом элемента), который бы не сделать структуру больше, чем доступ к объекту; смещение из массива должен оставаться элемент гибкого элемента массива, даже если это будет отличаться от такового замены матрицы. Если этот массив не будет содержать никаких элементов, он будет вести себя так, как если бы он имел один, но поведение undefined, если любая попытка сделать доступ этот элемент или сгенерировать указатель за ним.

Этот параграф содержит изменение в ¶20 стандарта ISO/IEC 9899: 1999/Cor.2: 2004 (E) - TC2 для C99;

Данные в конце основной части структуры, содержащей Элемент гибкого массива является регулярным отступлением, которое может любой тип структуры. Доступ к этому дополнению невозможен, но может быть передан библиотечные функции и т.д. посредством указателей на структуру без undefined.

Стандарт C11 содержит три примера, но первый и третий являются связанных с анонимными структурами и союзами, а не с механикой гибкие элементы массива. Помните, что примеры не являются "нормативными", но они являются иллюстративными.

¶20 ПРИМЕР 2 После объявления:

struct s { int n; double d[]; };

структура struct s имеет элемент гибкого массива d. Типичный способ использования:

int m = /* some value */;
struct s *p = malloc(sizeof (struct s) + sizeof (double [m]));

и при условии, что вызов malloc завершен, объект, на который указывает p ведет себя для большинства целей, как будто p был объявлен как:

struct { int n; double d[m]; } *p;

(существуют обстоятельства, при которых эта эквивалентность нарушена, в в частности, смещения члена d могут быть не одинаковыми).

¶21 Следуя приведенному выше заявлению:

struct s t1 = { 0 };          // valid
struct s t2 = { 1, { 4.2 }};  // invalid
t1.n = 4;                     // valid
t1.d[0] = 4.2;                // might be undefined behavior

Инициализация t2 недействительна (и нарушает ограничение) потому что struct s обрабатывается так, как если бы он не содержал член d. Назначение t1.d[0] - это, вероятно, поведение undefined, но оно возможно, что

sizeof (struct s) >= offsetof(struct s, d) + sizeof (double)

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

¶22 После следующего объявления:

struct ss { int n; };

выражения:

sizeof (struct s) >= sizeof (struct ss)
sizeof (struct s) >= offsetof(struct s, d)

всегда равны 1.

¶23 Если sizeof (double) равно 8, то после выполнения следующего кода:

struct s *s1;
struct s *s2;
s1 = malloc(sizeof (struct s) + 64);
s2 = malloc(sizeof (struct s) + 46);

и при условии, что вызовы malloc преуспевают, объекты, указывающие до s1 и s2 ведут себя для большинства целей, как если бы идентификаторы был объявлен как:

struct { int n; double d[8]; } *s1;
struct { int n; double d[5]; } *s2;

¶24 Следуя дальнейшим успешным назначениям:

s1 = malloc(sizeof (struct s) + 10);
s2 = malloc(sizeof (struct s) + 6);

они тогда ведут себя так, как если бы объявления были:

struct { int n; double d[1]; } *s1, *s2;

и

double *dp;
dp = &(s1->d[0]); // valid
*dp = 42;         // valid
dp = &(s2->d[0]); // valid
*dp = 42;         // undefined behavior

¶25 Назначение:

*s1 = *s2;

копирует только член n; если какой-либо из элементов массива находится внутри первые sizeof (struct s) байты структуры, они могут быть скопированы или просто перезаписаны неопределенными значениями.

Обратите внимание, что это изменилось между C99 и C11.

Другая часть стандарта описывает это поведение копирования:

§6.2.6 Представление типов §6.2.6.1 Общие сведения

¶6 Когда значение хранится в объекте структуры или типа объединения, в том числе в объекте-члене, байты представления объекта которые соответствуют любым байтам заполнения, не заданы значения. 51) Значение структуры или объекта объединения никогда не представляет собой ловушку, даже если значение члена структура или объект объединения может быть ловушечным представлением.

51) Таким образом, например, назначение структуры не обязательно копировать любые биты дополнений.

Иллюстрирование проблемной структуры FAM

В чат-чате C, Я написал некоторые информация о которой является перефразированной:

Рассмотрим:

struct fam1 { double d; char c; char fam[]; };

Предполагая, что double требует 8-байтового выравнивания (или 4 байта, это не имеет значения слишком много, но я буду придерживаться 8), тогда struct non_fam1a { double d; char c; }; будет иметь 7 заполняющих байтов после c и размер 16. Кроме того, struct non_fam1b { double d; char c; char nonfam[4]; }; имеют 3 байта заполнения после массива nonfam и размер 16.

Предполагается, что начало fam в struct fam1 может быть смещено 9, хотя sizeof(struct fam1) равно 16. Так что байты после c не заполняются (обязательно).

Итак, для достаточно маленького FAM размер структуры плюс FAM может все же быть меньше размера struct fam.

Прототипическое распределение:

struct fam1 *fam = malloc(sizeof(struct fam1) + array_size * sizeof(char));

когда FAM имеет тип char (как в struct fam1). Это (грубая) переоценка, когда смещение семьи меньше, чем sizeof(struct fam1).

Dror K. указал из:

Существуют макросы для вычисления "точного" требуемого хранилища основанные на смещениях FAM, которые меньше размера структуры. Например, этот: https://gustedt.wordpress.com/2011/03/14/flexible-array-member/

Решение вопроса

Вопрос спрашивает:

  • Используя гибкие члены массива (FAM) внутри типов структуры, мы разоблачаем наши программы возможностью поведения undefined?
  • Возможно ли, чтобы программа использовала FAM и все еще была строго Соответствующая программа?
  • Требуется ли смещение элемента гибкого массива на конец структуры?

Вопросы относятся как к C99 (TC3), так и к C11 (TC1).

Я считаю, что если вы правильно кодируете, ответы будут "Нет", "Да", "Нет и Да, в зависимости...".

Вопрос 1

Я предполагаю, что цель вопроса 1 - "должна ваша программа неизбежно подвергаются действию undefined, если вы используете какой-либо FAM в любом месте?" Чтобы заявить, что я думаю, очевидно: существует множество способов разоблачения программы для undefined (и некоторые из этих способов включают структуры с гибкими элементами массива).

Я не думаю, что просто использование FAM означает, что программа автоматически (вызывает, подвергается действию) undefined.

Вопрос 2

Раздел §4 Соответствие определяет:

¶5 Строго соответствующая программа должна использовать только те функции языка и библиотеки, указанных в этом Международном Стандартный. 3)Он не должен производить вывод, зависящий от любых неуказанных, undefined, или поведение, определяемое реализацией, и не должно превышать минимальный предел реализации.

3) Строго соответствующая программа может использовать условные (см. 6.10.8.3) при условии, что использование охраняется соответствующим условное включение препроцессорной директивы с использованием соответствующего макроса....

¶7 Соответствующая программа является приемлемой для соответствия осуществление. 5).

5) Строго соответствующие программы предназначены для максимально переносимый между соответствующими реализациями. Соответствующие программы могут зависеть от непереносимых особенностей соответствующая реализация.

Я не думаю, что есть какие-либо особенности стандарта C, которые, если они используются в путь, который намеревается стандарт, делает программу не строго соответствующий. Если они есть, они связаны с зависящим от локали поведения. Поведение кода FAM по своей сути не зависит от языка.

Я не думаю, что использование FAM по своей сути означает, что программа не строго соответствует.

Вопрос 3

Я думаю, что вопрос 3 неоднозначен между:

  • 3A: Требуется ли смещение элемента гибкого массива равным размер структуры, содержащей элемент гибкой матрицы?
  • 3B: Требуется ли смещение элемента гибкого массива больше чем смещение любого предыдущего элемента структуры?

Ответ на 3A - "Нет" (см. пример C11 в ¶25, приведенный выше).

Ответ на 3B - "Да" (свидетель §6.7.2.1 ¶15, приведенный выше).

Отказ от ответа Dror

Мне нужно процитировать стандарт C и Dror. Я использую [DK], чтобы указать начало цитаты из ответа Dror, а немаркированные цитаты из стандарта C.

По состоянию на 2017-07-01 18:00 -08: 00, короткий ответ Dror K сказал:

[DK]

  • Да. Общие соглашения об использовании FAM раскрывают наши программы для возможность поведения undefined. Сказав это, я не знаю любая существующая соответствующая реализация, которая будет ошибочной.

Я не уверен, что просто использование FAM означает, что программа автоматически имеет поведение undefined.

[DK]

  1. Возможно, но маловероятно. Даже если мы фактически не достигли undefinedповедение, мы все еще, вероятно, потерпим строгую совместимость.

Я не уверен, что использование FAM автоматически отображает программу не строго соответствует.

[DK]

  1. Нет. Смещение FAM не должно быть в конце struct, он может накладывать любые конечные байты заполнения.

Это ответ на мою интерпретацию 3A, и я согласен с этим.

В длинном ответе содержится интерпретация коротких ответов выше.

[DK]

Проблема заключалась в том, что обычные реализации C99, такие как GCC, не следуйте требованиям стандарта и разрешите FAM накладывать любые конечные байты заполнения. Их подход считался более и поскольку для них необходимо соблюдать требования Стандарт - приведет к нарушению обратной совместимости, комитет решил изменить спецификацию, а с C99 TC2 (ноябрь) 2004) стандарт больше не требовал, чтобы смещение FAM было на конец структуры.

Я согласен с этим анализом.

[DK]

Новая спецификация удалила оператор, требующий смещения FAM, чтобы быть в конце структуры, и она ввела очень неудачное последствие, поскольку стандарт дает реализацию свобода не сохранять значения любых байтов заполнения в пределах структур или союзов в согласованном состоянии.

Я согласен с тем, что в новой спецификации было исключено требование, чтобы FAM храниться со смещением, большим или равным размеру структура.

Я не согласен с тем, что есть проблема с байтами заполнения.

В стандарте явно указано, что назначение структуры для структуры содержащий FAM, фактически игнорирует FAM (§6.7.2.1 ¶18). Он должен скопировать членов, не являющихся членами FAM. В явном виде указано, что байты заполнения не нужно копировать вообще (§6.2.6.1 ¶6 и сноска 51). И пример 2 явно указывает (ненормативно, §6.7.2.1 ¶25) что если FAM перекрывает пространство, определенное структурой, данные от части FAM, которая перекрывается с концом структуры могут быть скопированы или не скопированы.

[DK]

Это означает, что если какой-либо из наших элементов FAM соответствует (или накладывается) любые конечные байты заполнения при сохранении члену struct- они (могут) принимают неуказанные значения. Нам даже не нужно обдумывать, относится ли это к значению, хранящемуся в самой FAM, даже строгая интерпретация, которая применяется только для членов, отличных от FAM, достаточно повреждает.

Я не вижу в этом проблемы. Любое ожидание того, что вы можете скопировать структуру, содержащую FAM, используя назначение структуры и копирование массива FAM является неотъемлемо недостаток - копия оставляет данные FAM логически незатронутыми. Любая программа, которая зависит от данных FAM в рамках структура нарушена; это свойство (ошибочной) программы, а не стандарт.

[DK]

#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>

int main(void) {
    struct s {
        size_t len;
        char pad;
        int array[];
    };

    struct s *s = malloc(sizeof *s + sizeof *s->array);

    if (sizeof *s > offsetof(struct s, array)) {
        s->array[0] = 123;
        s->len = 1; /* any padding bytes take unspecified values */

        printf("%d\n", s->array[0]); /* indeterminate value */
    }

    free(s);
    return 0;
}

В идеале, конечно, код будет устанавливать именованный элемент pad на детерминированное значение, но это не вызывает на самом деле проблемы поскольку он никогда не доступен.

Я категорически не согласен с тем, что значение s->array[0] в printf() является неопределенным; его значение равно 123.

Предварительная стандартная цитата (это то же самое §6.2.6.1 ¶6 в обоих C99 и C11, хотя номер сноски - 42 в C99 и 51 в C11):

Когда значение хранится в объекте структуры или типа объединения, в том числе в объекте-члене, байты представления объекта которые соответствуют любым байтам заполнения, принимают неопределенные значения.

Обратите внимание, что s->len не является назначением объекту структуры или тип объединения; это присваивание объекту типа size_t. Я думаю, что это может быть основным источником путаницы здесь.

Если код содержит:

struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
printf("t->array[0] = %d\n", t->array[0]);

то напечатанное значение действительно неопределенно. Однако, поскольку копирование структуры с помощью FAM не гарантируется для копирования FAM. Более правильный код будет (если вы добавите #include <string.h>, конечно):

struct s *t = malloc(sizeof(*t) + sizeof(t->array[0]));
*t = *s;
memmmove(t->array, s->array, sizeof(t->array[0]));
printf("t->array[0] = %d\n", t->array[0]);

Теперь напечатанное значение определено (это 123). Заметим, что условие на if (sizeof *s > offsetof(struct s, array)) не имеет значения для моего анализа.

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

[DK]

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

Это основано на ложной предпосылке; вывод ложный.

Ответ 3

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

Массивы не считаются внутренними, так что любое дополнение, которое добавляется из-за FAM, будет предшествовать ему. Если для размещения членов в FAM достаточно места или внутри структуры, они являются частью FAM. Например, данный:

struct { long long x; char y; short z[]; } foo;

размер "foo" может быть дополнен за пределами z из-за выравнивания, но любое такое дополнение будет использоваться как часть z. Написание y может нарушить заполнение, которое предшествует z, но не должно нарушать любую часть самого z.