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

Все ли указатели, полученные из указателей на типы структуры, одинаковы?

Вопрос

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

A. Отсутствие указателя на указатель на "любой" неполный или тип объекта накладывает ограничение на удобные функциональные интерфейсы, такие как:

int allocate(ANY_TYPE  **p,
             size_t    s);

int main(void)
{
    int *p;
    int r = allocate(&p, sizeof *p);
}

[Полный образец кода]

Существующий указатель на "любой" неполный или тип объекта явно описывается как:

C99/C11 §6.3.2.3 p1:

Указатель на void может быть преобразован в указатель или из указателя на любой неполный или тип объекта. [...]

Указатель, полученный из существующего указателя на "любой" неполный или тип объекта, указатель на указатель на void, является строго указателем на указатель на void и не требуется конвертировать с указателем, производным от указателя на ' любой "неполный или тип объекта.


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


Слова стандарта

Согласно C99 §6.2.5 p27/C11 §6.2.5 p28:

[...] Все указатели на типы структуры должны иметь одинаковые требования к представлению и выравниванию друг к другу. [...]

Далее следует C99 TC3 Footnote 39/C11 Footnote 48:

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

Хотя стандарт не говорит: "Указатель на тип структуры" и выбраны следующие слова: "Все указатели на типы структуры", он явно не указывает, относится ли это к рекурсивному выводу таких указатели. В других случаях, когда в стандарте упоминаются специальные свойства указателей, в нем явно не указывается или не упоминается рекурсивный вывод указателя, что означает, что применяется либо "деривация типа", либо нет, но он явно не упоминается.

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

4b9b3361

Ответ 1

Фон

Предполагается, что стандарт неявно требует, чтобы все указатели на типы структуры (полные, неполные, совместимые и несовместимые), чтобы иметь те же требования к представлению и выравниванию, начались на C89- много лет, прежде чем стандарт потребовал его явно. Обоснованием этого была совместимость неполных типов в отдельных единицах перевода, и, хотя, согласно комитету стандартов С, первоначальное намерение состояло в том, чтобы разрешить совместимость неполного типа с его завершенным изменением, фактические слова стандарта не описывали Это. Это было изменено во втором Техническом исправлении к C89 и поэтому сделало исходное предположение конкретным.


Совместимость и неполные типы

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

Вывод указателя объекта и неполных типов

C99/C11 §6.2.5 p1:

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

C99/C11 §6.2.5 p20:

Тип указателя может быть получен из типа функции, типа объекта или неполного типа, называемого ссылочным типом.

C99/C11 §6.2.5 p22:

Структура или тип объединения неизвестного содержимого является неполным. Он завершен для всех объявлений этого типа путем объявления той же структуры или тега union с его определяющим контентом позже в той же области.

Это означает, что указатели могут быть получены как из типов объектов, так и из неполных типов. Хотя не указано, что неполные типы не требуются для завершения; в прошлом комитет ответил на этот вопрос и заявил, что отсутствие запрета является достаточным, и нет необходимости в позитивном заявлении.

Следующий указатель на указатель на неполную "struct never_completed" никогда не завершается:

int main(void)
{
    struct never_completed *p;
    p = malloc(1024);
}

[Полный образец кода]

Совместимые типы отдельных единиц перевода

C99/C11 §6.7.2.3 p4:

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

C99/C11 §6.2.7 p1:

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

Этот параграф имеет большое значение, позвольте мне обобщить его: два типа структуры, объявленные в отдельных единицах перевода, совместимы, если они используют один и тот же тег. Если оба они завершены - их члены должны быть одинаковыми (в соответствии с указанными правилами).

Совместимость указателей

C99 §6.7.5.1 p2/C11 §6.7.6.1 p2:

Для двух типов указателей, которые должны быть совместимыми, оба должны быть одинаково квалифицированы, и оба должны быть указателями на совместимые типы.

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

C99/C11 §6.2.5 p20:

Любое число производных типов может быть построено из объекта, функции и неполных типов

Эти методы построения производных типов могут быть применены рекурсивно.

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

Представление совместимых типов

C99 §6.2.5 p27/C11 §6.2.5 p28:

указатели на квалифицированные или неквалифицированные версии совместимых типов должны иметь одинаковые требования к представлению и выравниванию.

C99/C11 §6.3 p2:

Преобразование значения операнда в совместимый тип не приводит к изменению значения или представления.

C99/C11 §6.2.5 p26:

Квалифицированные или неквалифицированные версии типа являются отдельными типами, принадлежащими к одной и той же категории, и имеют те же требования к представлению и выравниванию.

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

Следующий указатель на указатель неполный "struct complete_incomplete ':

struct complete_incomplete ** p;

Совместим и использует те же требования к представлению и выравниванию, что и следующий указатель на указатель на завершать "struct complete_incomplete ':

struct complete_incomplete {int i; } ** p;


Связанный с C89

Если мы зададимся вопросом о предпосылке относительно C89, вопрос о дефекте #059 от июня 93 'поставлен под сомнение:

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

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

Ответ комитета:

Непрозрачные типы данных были рассмотрены и одобрены Комитетом при разработке Стандарта C.


Совместимость по сравнению с взаимозаменяемостью

Мы рассмотрели аспект, касающийся требований представления и выравнивания рекурсивного вывода указателей указателей к типам структур, теперь мы сталкиваемся с вопросом, который упоминается в ненормативной сноске: "взаимозаменяемость":

C99 TC3 §6.2.5 p27 Footnote 39/C11 §6.2.5 p28 Footnote 48:

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

В стандарте говорится, что примечания, сноски и примеры являются ненормативными и являются "только для информации".

C99 FOREWORD p6/C11 FOREWORD p8:

[...] это предисловие, введение, заметки, сноски и примеры также доступны только для информации.

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

Совместимость указателей с типами структуры

C99/C11 §6.7.2.3 p4:

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

C99/C11 §6.2.7 p1:

Два типа имеют совместимый тип, если их типы одинаковы.

C99 §6.7.5.1 p2/C11 §6.7.6.1 p2:

Для двух типов указателей, которые должны быть совместимыми, оба должны быть одинаково квалифицированы, и оба должны быть указателями на совместимые типы.

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

Эффективные типы

C99/C11 §6.5 p7:

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

тип, совместимый с эффективным типом объекта

C99/C11 §6.5 p6:

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

Несовместимые указатели не являются "взаимозаменяемыми" в качестве аргументов для функций, а также как возвращаемые значения из функций. Исключением являются неявные преобразования и указанные особые случаи, и эти типы не являются частью какого-либо такого исключения. Даже если мы решили добавить нереалистичное требование для упомянутой "взаимозаменяемости" и сказать, что для его применения требуется явное преобразование, тогда доступ к сохраненному значению объекта с несовместимым эффективным типом нарушает эффективных типов правил. Для того чтобы это стало реальностью, нам нужно новое свойство, которого в настоящее время нет в стандарте. Поэтому, разделяя те же требования к представлению и выравниванию, и будучи конвертируемыми, просто недостаточно.

Это оставляет нас взаимозаменяемыми "членами профсоюзов", и хотя они действительно взаимозаменяемы как члены союза - это не имеет особого значения.

Официальные интерпретации

1. Первая "официальная" интерпретация принадлежит члену Комитета по стандартам С. Его интерпретация для: "подразумевает взаимозаменяемость", заключается в том, что она фактически не подразумевает, что такая взаимозаменяемость существует, но на самом деле делает для нее предложение.

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

2. Вторая "официальная" интерпретация принадлежит члену/вкладчику в комитет по стандартам С, по его интерпретации сноска не вводит предложения, а потому (нормативный) текст стандарт не подразумевает этого, он считает это дефектом в стандарте. Он даже сделал предложение изменить правила эффективных типов для решения этого вопроса.

3. Третья "официальная" интерпретация - из отчета о дефектах #070 от 93 декабря. В контексте C89 было задано вопрос о том, должна ли программа, которая передает тип "unsigned int", где ожидается тип "int", в качестве аргумента функции с не-прототипом декларатора, ввести undefined поведение.

В C89 есть та же самая сноска с той же подразумеваемой взаимозаменяемостью, что и аргументы для функций, прикрепленные к:

C89 §3.1.2.5 p2:

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

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


Следующий пример кода не соответствует строгому соответствию. '& s1' и 'struct generic **' используют одни и те же требования к представлению и выравниванию, но тем не менее они несовместимы. В соответствии с правилами эффективных типов мы обращаемся к хранимому значению объекта 's1' с несовместимым эффективным типом, указателем на 'struct generic', а его объявленный тип и, следовательно, эффективный тип, является указателем на 'struct s1 ". Чтобы преодолеть это ограничение, мы могли бы использовать указатели как члены союза, но это соглашение наносит ущерб цели генерации.

int allocate_struct(void    *p,
                    size_t  s)
{
    struct generic **p2 = p;
    if ((*p2 = malloc(s)) == NULL)
        return -1;

    return 0;
}

int main(void)
{
    struct s1 { int i; } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return EXIT_FAILURE;
}

[Полный образец кода]


Следующий пример кода строго соответствует, чтобы преодолеть обе проблемы эффективных типов и быть универсальными, мы используем: 1. указатель на void, 2. требования к представлению и выравниванию всех указателей на структуры и 3. доступ к представлению байтов указателя "в общем", при использовании memcpy для копирования представления без влияния на его эффективный тип.

int allocate_struct(void    *pv,
                    size_t  s)
{
    struct generic *pgs;

    if ((pgs = malloc(s)) == NULL)
        return -1;

    memcpy(pv, &pgs, sizeof pgs);
    return 0;
}

int main(void)
{
    struct s1 { int i; } *s1;

    if (allocate_struct(&s1, sizeof *s1) != 0)
        return EXIT_FAILURE;
}

[Полный образец кода]


Заключение

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

Ответ 2

Мой ответ "нет".

В любом стандарте C нет формулировки, о которой я знаю, о которой говорится иначе. Тот факт, что все указатели на типы структуры имеют одинаковые требования к представлению и выравниванию, не имеет отношения к любому производному типу.

Это делает полный смысл, и любая другая реальность казалась бы непоследовательной. Рассмотрим альтернативу:

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

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

Предположим, что "A" и "B" не совпадают [1]. Кроме того, пусть они не могут быть выполнены одновременно. (Например, 4-байтовое представление и 8-байтовое представление.)

Теперь выведите тип из обоих:

  • Тип с требованиями "A"
  • Тип с требованиями "B"

Теперь у вас есть тип, требования которого невозможно удовлетворить, потому что он должен удовлетворять "A" и "B" , но они не могут одновременно выполняться одновременно.

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

[1] Хотя это может показаться необоснованным, маловероятным и глупым, это разрешено.