Все структурные идентификаторы объявляются автоматически

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

Например, программа ниже хорошо компилируется:

/* Compile with "gcc -std=c99 -W -Wall -O2 -pedantic %" */
#include <stdio.h>

struct foo 
{
    struct bar *next;  /* Linked list */
};


int main(void) {
    struct bar *a = 0;
    struct baz *b = 0;
    struct foo c = {0};

    printf("bar -> %p\n", (void *)a);
    printf("baz -> %p\n", (void *)b);
    printf("foo -> %p, %zu\n", (void *)&c, sizeof c); /* Remove %zu if compiling with -ansi flag */
    return 0;
}

Мой вопрос: какое правило направляет C компилятор для обработки необъявленного struct identifierкак форвард объявлен неполным struct типы?

4 ответа

Решение

Стандарт гласит ( 6.2.5.28)

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

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

Это описано в 6.2.5 Типы и 6.7.2.3 Теги.

struct identifier это тип объекта.

6.2.5 Типы

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

37) Тип может быть неполным или полным во всей единице перевода или может изменять состояния в разных точках в единице перевода.

  1. Тип массива неизвестного размера является неполным типом. Для идентификатора этого типа он завершается указанием размера в последующем объявлении (с внутренней или внешней связью). Тип структуры или объединения с неизвестным содержимым (как описано в 6.7.2.3) является неполным типом. Он завершается для всех объявлений этого типа объявлением той же структуры или тега объединения с его определяющим содержимым позже в той же области видимости.

6.7.2.3 Теги

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

129) Неполный тип может использоваться только тогда, когда размер объекта этого типа не требуется. Это не требуется, например, когда имя typedef объявляется как спецификатор для структуры или объединения, или когда объявляется указатель или функция, возвращающая структуру или объединение. (См. Неполные типы в 6.2.5.) Спецификация должна быть завершена до вызова или определения такой функции.

Помимо ответа, предоставленного 2501, и вашего комментария к нему, что " В моем случае нет даже предварительной декларации ", следующее.

Любое использование struct tag считается (предварительным) объявлением типа структуры, если оно не было объявлено ранее. Хотя более формальным способом было бы сказать, что это просто считается типом, так как стандарт C не упоминает "предварительные объявления типов структуры", а только полные и неполные типы структуры (6.2.5p22).

6.7.2 Спецификаторы типа говорят нам, что спецификатор struct-or-union является спецификатором типа, а 6.7.2.1. Спецификатор структуры и объединения параграф 1 говорит нам, что этот идентификатор структуры в свою очередь является спецификатором struct-or-union.

Предположим, у вас есть объявление связанного списка, что-то вроде

struct node {
    struct node *next;
    int element;
};

тогда "неявное прямое объявление" этого неполного типа необходимо для работы этой структуры. В конце концов, тип struct node завершается только в конце точки с запятой. Но вы должны обратиться к нему, чтобы объявить next указатель.

Также struct node Объявление (неполного типа) может выходить за рамки, как и любое другое объявление. Это происходит, например, если у вас есть прототип

int function(struct unknown *parameter);

где struct unknown сразу выходит из области видимости в конце декларации. Любое дальнейшее заявлено struct unknown тогда они не такие, как этот. Это подразумевается в тексте 6.2.5p22:

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

Вот почему gcc предупреждает об этом:

foo.c:1:21: warning: 'struct unknown' declared inside parameter list
foo.c:1:21: warning: its scope is only this definition or declaration, which is probably not what you want

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

struct unknown;
int function(struct unknown *parameter);

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

struct foo 
{
    struct bar *left;
    struct bar *right;
};
struct bar
{
    int something;
    struct foo *next;
};

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

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

Изменить: После комментария о документации, давайте посмотрим на библию языка C: Kerninghan&Ritchie - Язык программирования C, раздел "6.5 Само-ссылочные структуры" говорит:

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

struct t {
    ...
    struct s *p;   /* p points to an s */
};
struct s {
    ...
    struct t *q;   /* q points to a t */
};

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

Другие вопросы по тегам