Если тип структуры определен в другом файле .c, он становится неполным?

У меня не может быть проблем с этим кодом:

#include <stdio.h>
struct foo
{
  void * data;
};

int main()
{
  printf("%ul\n", sizeof(struct foo));
}

но как только структура будет объявлена ​​в другом файле и передана компилятору, она волшебным образом станет неполной:

РЕДАКТИРОВАТЬ (я не предоставил весь код):

b.h:

struct foo

b.c:

#include "b.h"
struct foo
{
    void * data;
};

a.c:

#include <stdio.h>
#include "b.h"

int main()
{
    printf("%lu\n",sizeof(struct foo));
}

сработало: $gcc a.c b.c

все та же ошибка:

error: invalid application of ‘sizeof’ to incomplete type ‘struct foo’
  printf("%lu\n",sizeof(struct foo));

person milanHrabos    schedule 20.08.2020    source источник
comment
Здесь не так много магии. При компиляции a.c как компилятор узнает, что такое struct foo? Для этого и нужны заголовочные файлы.   -  person stark    schedule 20.08.2020
comment
stackoverflow.com/questions/13634495/c-typedef-incomplete-type - этого будет достаточно?   -  person Antti Haapala    schedule 20.08.2020
comment
@stark Извините, я не предоставил весь источник, пожалуйста, вернитесь к вопросу еще раз, ошибка все еще существует   -  person milanHrabos    schedule 20.08.2020
comment
@AnttiHaapala тебе того же   -  person milanHrabos    schedule 20.08.2020
comment
Объявление struct foo; (у вас отсутствует точка с запятой) называется предварительным объявлением. Он говорит, что этот тип существует, но не определяет его.   -  person stark    schedule 20.08.2020


Ответы (4)


Единица перевода, т. е. один .c файл и его #included .h файлов, должны быть автономными и содержать в требуемом порядке все определения и декларации, необходимые для компиляции единицы перевода. Даже если вы предоставляете несколько файлов .c в командной строке GCC, каждый из этих файлов считается отдельной единицей перевода.

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

Стандарт C11/C18 гласит: (6.5.3.4p1) что

Оператор sizeof не должен применяться к выражению функционального или неполного типа [...]

И (6.7.2.3p4)

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

Со сноской 129, в которой говорится, что

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

т. е. ваша единица перевода a.c состоит из следующего кода:

// code included from <stdio.h>
...
// code included from "b.h"
struct foo;

// rest of code in a.c
int main()
{
    printf("%lu\n",sizeof(struct foo));
}

Это все, что компилятор C знает о struct foo, и к тому времени, когда он достигает sizeof(struct foo), к 6.7.2.3p4 тип struct foo все еще em> неполный и выдается ошибка.

В качестве исправления b.h вместо совершенно бесполезного и неэффективного struct foo; должно иметь фактическое определение структуры:

b.h:

struct foo
{
    void * data;
};
person Antti Haapala    schedule 20.08.2020
comment
Зачем тогда использовать static для данных, когда у разных единиц перевода есть свои уникальные данные? Означает ли это, что у меня может быть несколько структур с одним и тем же тегом в разных TU? потому что, согласно вашему ответу, структура должна быть определена для каждого TU, что означает, что у меня может быть несколько структур с одним и тем же именем в разных TU. - person milanHrabos; 20.08.2020
comment
@milanHrabos да, вы можете использовать один и тот же тег структуры для разных структур в разных TU. Если вы хотите использовать одно и то же (совместимый тип) определение, они должны использовать тег same и иметь то же самое определение. Что касается статических данных, если не static, переменная области файла будет иметь внешнюю связь, и ее можно будет увидеть в другой единице перевода. Однако другая единица перевода должна объявить, чтобы идентификатор был виден (extern int foo;). - person Antti Haapala; 20.08.2020

Компилятор не использует несколько исходных файлов. Каждый исходный файл независимо компилируется в объектные файлы. На этапе компиляции a.c и b.c неизвестны друг другу. Таким образом, a.c не может быть скомпилировано в объект, поскольку это зависит от знания того, что такое struct foo.

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

person Christian Gibbons    schedule 20.08.2020

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

Когда вы используете

printf("%lu\n",sizeof(struct foo));

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

Но это не выполняется, так как struct foo объявляется только вперед во включенном файле b.h.

Предварительное объявление struct foo допустимо, но struct foo является неполным типом, пока он не был объявлен на самом деле. В частности, это касается отдельных ВПУ.

Объявление struct foo в b.c для того, чтобы сделать struct foo полным типом, не отображается в a.c, когда к нему применяется оператор sizeof; отсюда и ошибка.


Возможные решения):

  1. Поменяйте местами предварительное объявление struct foo с объявлением struct foo внутри b.h и опустите объявление ´struct foo в b.c.

  2. #include "b.c" в a.c перед применением оператора sizeof при вызове printf().

  3. Просто объявите struct foo в a.c. :-)

person RobertS supports Monica Cellio    schedule 20.08.2020
comment
Зачем тогда использовать static для данных, когда у разных единиц перевода есть свои уникальные данные? Означает ли это, что у меня может быть несколько структур с одним и тем же тегом в разных TU? потому что, согласно вашему ответу, структура должна быть определена для каждого TU, что означает, что у меня может быть несколько структур с одним и тем же именем в разных TU. - person milanHrabos; 20.08.2020

Если тип структуры определен в другом файле .c, он становится неполным?

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

В единице перевода, содержащей этот код

#include <stdio.h>

int main()
{
    printf("%lu\n",sizeof(struct foo));
}

struct foo не определено.

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

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

Или, если вам нужно знать только размер структуры и она больше нигде не используется, то вы можете просто написать

#include <stdio.h>

int main()
{
    printf("%zu\n",sizeof(struct foo { void * data; } ));
}

Обратите внимание, что в приведенной выше программе тип struct foo определяется в области видимости блока main.

EDIT: После того, как вы обновили свой вопрос, затем в этой единице перевода

#include <stdio.h>
#include "b.h"

int main()
{
    printf("%lu\n",sizeof(struct foo));
}

у вас неполное объявление структуры в заголовке b.h (где вы забыли поставить точку с запятой после объявления)

struct foo;
         ^^^

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

Обратите внимание на то, что в этой команде

$gcc a.c b.c

файлы a.c и b.c компилируются отдельно.

person Vlad from Moscow    schedule 20.08.2020
comment
что, если структура немного сложнее, а файлы скомпилированы с другими параметрами компиляции? - person 0___________; 20.08.2020
comment
@P__J__ В моем ответе все ясно написано. Перечитайте еще раз, если не поняли. - person Vlad from Moscow; 20.08.2020
comment
@VladfromMoscow Я отредактировал вопрос, пожалуйста, пересмотрите его. - person milanHrabos; 20.08.2020