Структуры, объявленные в прототипах функций

У меня есть две функции. 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), с другой стороны, не скомпилируется. Почему первый работает, а второй нет? Каковы возможные варианты использования этих "мода"?

struct point внутри #if 0 нигде не определен

stark 04.06.2024 17:46

@stark struct point { int x, y; } в bar()? Но, как говорят автор, GCC и Clang, это определение не будет видно за пределами bar().

Harith 04.06.2024 17:46

@stark Я прямо упомянул, что первый случай (struct foo) компилируется отлично, а второй случай (struct point) нет, в то время как автор сообщения утверждает, что область действия структуры, объявленной ни в одном из этих модов, не распространяется за пределы объявление или определение функции. Если то, что говорит автор, верно, согласно предоставленным им ссылкам, то первый случай также не следует компилировать.

Harith 04.06.2024 17:51

Автор поста ошибается насчет первого случая.

Ian Abbott 04.06.2024 17:55

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

Ian Abbott 04.06.2024 18:06

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

Craig Estey 04.06.2024 19:11

Что касается связанного блога, я однажды пытался создать вики, перечисляющую все различия между новостями в C99 (по сравнению с C89) и C++17, здесь: Все ли функции C99 также есть в C++?

Lundin 05.06.2024 12:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
7
143
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Давайте подробнее рассмотрим версию 6.2.1p4 C99. Говорится:

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

struct point подпадает под эту категорию, поскольку он объявлен как часть списка параметров. struct foo однако не является частью списка параметров, а является типом возвращаемого значения функции. Несколькими предложениями выше в версии 6.2.1 мы также имеем это:

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

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

Понятно, но какой смысл допускать второй случай (struct point)? Есть ли у него какая-то польза, которую я не вижу?

Harith 04.06.2024 18:01

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

Andrew Truckle 04.06.2024 18:03

@AndrewTruckle Если вызывающая функция ничего не знает о struct point, как она может передать такой struct в bar()?

Harith 04.06.2024 18:10

@Харит Нет смысла разрешать второй случай. Просто это не было явно запрещено спецификацией языка.

Ian Abbott 04.06.2024 18:13

@Harith Это существует, по крайней мере, с C89, так что для этого может быть какое-то историческое основание.

dbush 04.06.2024 18:15

@Харит, почему вызов не может повторно объявить его так, как он его называет? Я не знаю 🤷

Andrew Truckle 04.06.2024 18:53
Ответ принят как подходящий

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

Прототип функции (Стандарт C 6.2.1 Области действия идентификаторов):

  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 Области действия идентификаторов)

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

и

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

Читайте настоящий стандарт (или, по крайней мере, окончательный вариант), а не случайные сообщения в блогах.

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

«Прочитайте настоящий стандарт» ==> В большинстве случаев это непростое чтение. :)

Harith 04.06.2024 18:03

Дэвид Р. Триббл [...] говорит, что область действия структуры, объявленной любым из этих способов, не выходит за пределы объявления или определения функции,

Как заметили другие, Триббл неправ в этом отношении...

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), но не обязательно несовместимые.

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