Странное предупреждение компилятора C: warning: 'struct объявлен внутри списка параметров

Я просто нашел причуду в C, которую я нахожу очень запутанной. В C можно использовать указатель на структуру до ее объявления. Это очень полезная функция, которая имеет смысл, потому что объявление не имеет значения, когда вы просто имеете в виду указатель на него. Я только что нашел один угловой случай, когда это (удивительно) не правда, и я не могу объяснить, почему. Для меня это выглядит ошибкой в ​​дизайне языка.

Возьмите этот код:

#include <stdio.h>

#include <stdlib.h>


typedef void (*a)(struct lol* etc);

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}

дает:

foo.c:6:26: warning: ‘struct lol’ declared inside parameter list [enabled by default]
foo.c:6:26: warning: its scope is only this definition or declaration, which is probably not what you want [enabled by default]
foo.c:8:16: warning: ‘struct lol’ declared inside parameter list [enabled by default]

Чтобы устранить эту проблему, мы можем просто сделать это:

#include <stdio.h>

#include <stdlib.h>

struct lol* wut;

typedef void (*a)(struct lol* etc);

void a2(struct lol* etc) {

}

int main(void) {
        return 0;
}

Необъяснимая проблема теперь исчезла по необъяснимой причине. Зачем?

Обратите внимание, что этот вопрос касается поведения языка C (или возможного поведения компилятора gcc и clang), а не конкретного примера, который я вставил.

EDIT:

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

Ответы

Ответ 1

Чтобы понять, почему компилятор жалуется, вам нужно знать две вещи о C "struct" s:

  • они создаются (как объявленный, но еще не определенный тип), как только вы их назовете, поэтому самое первое появление struct lol создает объявление
  • они подчиняются тем же правилам области декларации, что и обычные переменные

(struct lol { объявляет, а затем начинает определять структуру, это struct lol; или struct lol * или что-то еще, у которого нет открытой скобки, которая останавливается после шага "объявить".)

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

struct lol *global_p;
void f(void) {
    use0(global_p);     /* this is OK */
    use1(*global_p);       /* this is an error */
    use2(global_p->field); /* and so is this */
}

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

В любом случае рассмотрим объявления функций с обычными параметрами int:

int imin2(int a, int b); /* returns a or b, whichever is smaller */
int isum2(int a, int b); /* returns a + b */

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

То же самое происходит с тегами struct:

void gronk(struct sttag *p);

struct sttag объявляет структуру, а затем декларация сметена, как и те, что указаны для a и b. Но это создает большую проблему: тег пропал, и теперь вы больше не можете назвать тип структуры! Если вы пишете:

struct sttag { int field1; char *field2; };

который определяет новый и другой struct sttag, например:

void somefunc(int x) { int y; ... }
int x, y;

определяет новые и разные x и y в области уровня файлового уровня, отличные от тех, что указаны в somefunc.

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

struct sttag;
void gronk(struct sttag *p);

Теперь оба struct sttag являются "теми же" struct sttag, поэтому, когда вы закончите struct sttag позже, вы также завершите его внутри прототипа для gronk.


Вернемся к вопросу: возможно, было бы возможно определить действие тегов struct, union и enum по-разному, сделав их "пузырьками" прототипов к их охватывающим областям. Это заставит проблему уйти. Но это не было определено именно так. Поскольку это был комитет ANSI C89, который изобрел (или украл, действительно, из-за-С++) прототипы, вы можете обвинить его в них.: -)

Ответ 2

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

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

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

Ответ 3

Компилятор предупреждает вас о прямом объявлении struct lol. C позволяет вам сделать это:

struct lol;     /* forward declaration, the size and members of
                   struct lol are unknown */

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

void foo(struct lol *x);

Однако просто использование незадекларированной структуры в объявлении функции, как и вы, будет интерпретироваться как локальное неполное объявление struct lol, область видимости которого ограничена функцией. Эта интерпретация предусмотрена стандартом C, но это не полезно (нет способа построить struct lol для перехода к функции) и почти наверняка не то, что программист намеревался, поэтому компилятор предупреждает.