У меня есть две функции. pos(), который определяет struct как тип возвращаемого значения, и bar(), который определяет struct как тип аргумента:
#include <stdio.h>
#include <stdlib.h>
static struct foo { int x; } pos(void)
{
return (struct foo) {10};
}
static int bar(struct point { int x, y; } pt)
{
return pt.x + pt.y;
}
int main(void)
{
struct foo a = pos();
printf("a.x: %d.\n", a.x);
#if 0
int sum = bar((struct point){10, 20});
printf("sum: %d.\n", sum);
#endif
return EXIT_SUCCESS;
}
О котором здесь Дэвид Р. Триббл Несовместимость между ISO C и ISO C++ говорит, что область действия структуры, объявленной в любом из этих способов, не выходит за пределы объявление или определение функции, что делает невозможным определение объектов этого типа структуры, который может быть передан в качестве аргументов функции или присваивать возвращаемые значения функции объектам этого типа и предлагает [C99: §6.2.1, 6.7.2.3, 6.7.5.3, I] в качестве ссылок.
Но первый (struct foo) компилируется и работает отлично (т. е. я могу определять объекты этого типа структуры в main(), могу присваивать возвращаемое значение функции объекту этого типа). GCC 13.1 и Clang 18.1 даже не
вынести какое-либо предупреждение.
Последний (struct point), с другой стороны, не скомпилируется. Почему первый работает,
а второй нет? Каковы возможные варианты использования этих
"мода"?
@stark struct point { int x, y; } в bar()? Но, как говорят автор, GCC и Clang, это определение не будет видно за пределами bar().
@stark Я прямо упомянул, что первый случай (struct foo) компилируется отлично, а второй случай (struct point) нет, в то время как автор сообщения утверждает, что область действия структуры, объявленной ни в одном из этих модов, не распространяется за пределы объявление или определение функции. Если то, что говорит автор, верно, согласно предоставленным им ссылкам, то первый случай также не следует компилировать.
Автор поста ошибается насчет первого случая.
C допускает оба случая, но автор прав в том, что определение типа структуры в списке параметров ограничивает область определения, действительную только внутри функции. Определение типа структуры в объявлении типа возвращаемого значения функции не ограничивает область действия типа структуры в C.
Хотя это может быть законно, использование функции, определяющей структуру, имеет ограниченную полезность. Для struct point [если бы вы вообще могли это сделать], вы можете передать структуру только по значению. Этого почти никогда не делается. Чтобы понять почему, попробуйте: struct point { int x,y; int data[1000000000]; } Какова настоящая цель? Иметь непрозрачные структуры? Если да, поставьте struct foo; в .h. Тогда пользователи этого смогут только передавать struct foo *, но не разыменовывать его. В foo.c выполните: struct foo { int x,y; int data[1000000000]; }; Тогда любая функция в foo.c сможет разыменовать структуру.
Что касается связанного блога, я однажды пытался создать вики, перечисляющую все различия между новостями в C99 (по сравнению с C89) и C++17, здесь: Все ли функции C99 также есть в C++?





Давайте подробнее рассмотрим версию 6.2.1p4 C99. Говорится:
Если декларатор или спецификатор типа, объявляющий идентификатор появляется в списке объявлений параметров в функции прототип (не часть определения функции), идентификатор имеет область действия прототипа функции, которая завершается в конце функции декларатор.
struct point подпадает под эту категорию, поскольку он объявлен как часть списка параметров. struct foo однако не является частью списка параметров, а является типом возвращаемого значения функции. Несколькими предложениями выше в версии 6.2.1 мы также имеем это:
Если декларатор или спецификатор типа, объявляющий идентификатор появляется вне любого блока или списка параметров, идентификатор имеет область файла, которая заканчивается в конце единицы перевода.
Поскольку struct foo объявляется вне блока и вне списка параметров, он имеет область действия файла и может использоваться в любом месте исходного файла после его объявления.
Понятно, но какой смысл допускать второй случай (struct point)? Есть ли у него какая-то польза, которую я не вижу?
@Harith Возможно, вы хотите объединить несколько значений в структуру для удобства чтения и будете использовать структуру только в этой функции.
@AndrewTruckle Если вызывающая функция ничего не знает о struct point, как она может передать такой struct в bar()?
@Харит Нет смысла разрешать второй случай. Просто это не было явно запрещено спецификацией языка.
@Harith Это существует, по крайней мере, с C89, так что для этого может быть какое-то историческое основание.
@Харит, почему вызов не может повторно объявить его так, как он его называет? Я не знаю 🤷
Начнем с того, что прототип функции и область действия прототипа функции — это два разных понятия.
Прототип функции (Стандарт C 6.2.1 Области действия идентификаторов):
- ...Прототип функции — это объявление функции, в которой объявляется типы его параметров.
Такое объявление функции может быть одновременно определением функции.
Что касается области действия прототипа функции, то
Если декларатор или спецификатор типа, объявляющий идентификатор появляется в списке объявлений параметров в функции прототип (не часть определения функции), идентификатор имеет область действия прототипа функции, которая завершается в конце функции декларатор
То есть цитата относится к прототипам функций, которые не являются одновременно их определениями.
Обратите внимание, что в вашем примере программы у вас нет Область действия прототипа функции.
Структура struct foo объявлена в области файла.
static struct foo { int x; } pos(void)
{
return (struct foo) {10};
}
Итак, это видно с точки зрения декларации.
Что касается структуры struct point, то она объявлена в области видимости блока функции bar
static int bar(struct point { int x, y; } pt)
{
return pt.x + pt.y;
}
Поэтому он невидим вне функции.
Из стандарта C (6.2.1 Области действия идентификаторов)
Если декларатор или спецификатор типа, объявляющий идентификатор появляется вне любого блока или списка параметров, идентификатор имеет область файла, которая заканчивается в конце единицы перевода.
и
- ... Если декларатор или спецификатор типа, объявляющий идентификатор появляется внутри блока или в списке объявлений параметров в определение функции, идентификатор имеет область действия блока, что завершается в конце соответствующего блока.
Читайте настоящий стандарт (или, по крайней мере, окончательный вариант), а не случайные сообщения в блогах.
Если декларатор или спецификатор типа, который объявляет идентификатор, появляется внутри блока или в списке объявлений параметров в определении функции, идентификатор имеет область действия блока, которая заканчивается в конце связанного блока. Если декларатор или спецификатор типа, который объявляет идентификатор, появляется в списке объявлений параметров в прототипе функции (не является частью определения функции), идентификатор имеет область действия прототипа функции, которая заканчивается в конце декларатора функции.
«Прочитайте настоящий стандарт» ==> В большинстве случаев это непростое чтение. :)
Дэвид Р. Триббл [...] говорит, что область действия структуры, объявленной любым из этих способов, не выходит за пределы объявления или определения функции,
Как заметили другие, Триббл неправ в этом отношении...
static struct foo { int x; } pos(void) { // ... }
... и в этом отношении он тоже был бы неправ...
static struct foo { int x; } pos(void);
. В обоих случаях тип struct foo объявляется в области файла, и поэтому его можно использовать в другом месте TU для объявления объектов этого типа, например, для получения возвращаемого значения от вызова этой функции.
делая невозможным определение объектов этого типа структуры, которые могут быть переданы в качестве аргументов функции, или присвоение возвращаемых значений функции объектам этого типа.
Триббл в лучшем случае вводит в заблуждение по этому поводу. C требует, чтобы аргументы функций с прототипами в области видимости имели тип, совместимый по присваиванию с объявленными типами соответствующих параметров (C99 6.5.2.2). Это не обязательно означает тот же тип* или даже версию того же типа с разной квалификацией. Для параметров, объявленных со структурой или типом объединения, или типами, производными от таких типов, это очень часто не так.
В частности, совместимость по присваиванию (C99 6.5.16) для типов структур и объединений, а также производных от них типов основана на общем определении «совместимого типа» (C99 6.2.7). Два типа структур, объявленные в разных TU, совместимы, если они согласуются в
и если оба являются полными типами,
Это, безусловно, удовлетворяется, если два определения лексически идентичны, например, из содержащего его заголовка #including, но это не существенно.
Я сказал, что Триббл вводит в заблуждение, а не совсем ошибается. Технически он прав, потому что
правила совместимости типов структур охватывают один и тот же тип и типы, объявленные в разных TU. Они не охватывают несколько определений в одном и том же ТУ, даже если их области применения не пересекаются. Таким образом, в C99, если существует определение структуры с областью действия прототипа блока или функции, то нет возможности объявить совместимый тип структуры где-либо еще в том же TU. И,
рассматриваемые функции объявлены static, поэтому их нельзя вызвать из другого ТУ.
ОДНАКО,
В противном случае идентичные внешние функции могут быть вызваны с соответствующими аргументами из другого TU, даже в соответствии с правилами C99 (с некоторой осторожностью в их объявлении).
Эта особенность правил совместимости в значительной степени является формальностью, и она будет удалена в C2X для типов тегированных структур, таких как те, о которых идет речь (C2X 6.2.7/1).
До C2X вы, скорее всего, на практике встретите правило совместимости типов C2X, реализованное в виде расширения компиляторами C, хотя бы по той причине, что его проще поддерживать, чем не поддерживать.
и предлагает [C99: §6.2.1, 6.7.2.3, 6.7.5.3, I] в качестве ссылок.
И это показывает вам, что не следует доверять кому-то только потому, что он цитирует ссылки (поэтому проверьте мои). Заявления, которые вы приписываете Tribble, похоже, основаны на интерпретациях спецификации, которые неверны в нескольких деталях:
Однако с учетом вышесказанного статья Триббла в целом кажется довольно хорошей. Я думаю, что этот вопрос затрагивает самое важное больное место.
* Объявления типов структур или объединений в разных областях объявляют разные типы (C99 6.7.2.3/5), но не обязательно несовместимые.
struct pointвнутри#if 0нигде не определен