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

Вопрос

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

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

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 и не требуется быть конвертируемым с указателем, производным от указателя на "любой" неполный или тип объекта.


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


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

по данным C99 §6.2.5 p27 / C11 §6.2.5 p28:

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

следовал по C99 TC3 Footnote 39 / C11 Footnote 48:

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

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

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

2 ответов


фон

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


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

читая рекомендации, связанные с совместимостью и неполными типами, благодаря Matt McNabb, мы находим дальнейшее понимание оригинального C89 предположение.

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

C99 / C11 §6.2.5 p1:

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

C99 / C11 §6.2.5 p20:

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

C99 / C11 §6.2.5 p22:

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

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

следующий указатель на указатель на неполный "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:

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

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

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

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:

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

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

следующий указатель на указатель на неполное 'структуру complete_incomplete':

struct complete_incomplete * * p;

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

struct complete_incomplete { int i;} * * p;


С89, связанных с

если мы задаемся вопросом о предпосылке, касающейся 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. первое "официальное" толкование принадлежит члену комитета по стандартам C. Его толкование для: "подразумевается взаимозаменяемость", заключается в том, что это на самом деле не означает, что такая взаимозаменяемость существует, но на самом деле делает предложение для него.

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

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

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

в 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;
}

[полный пример кода]


Вывод

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


мой ответ - "нет."

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

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

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

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

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

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

  1. тип с требованиями "A"
  2. тип A с требованиями"B"

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

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