переопределение typedef

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

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

// in A.h
typedef struct A_ A;
typedef struct B_ B;
struct A_ {
    double a;
    B *b;
};

// in B.h
typedef struct B_ B;
struct B_ {
    int c;
};

// in C.h
typedef struct A_ A;
typedef struct B_ B;
void function_do_something(A*, B*);

// in C.c
#include "A.h"
#include "B.h"
#include "C.h"
void function_do_something(A* a, B* b) {
    ...
}

Эта парадигма компилируется и работает в Ubuntu 11.10 gcc, но выдает ошибки компилятора в OpenSUSE gcc, которые говорят о «переопределении typedef».

Я занимался разработкой в ​​Ubunutu и поэтому не осознавал, что эта парадигма может быть неверной. Просто это неправильно, и gcc в Ubuntu слишком хорош?


person BrT    schedule 03.12.2011    source источник
comment
@Charles извините, C.c также должен включать A.h и B.h, чтобы он знал о содержимом структуры.   -  person BrT    schedule 03.12.2011
comment
Вот почему я хочу заменить препроцессор C на php или любой другой язык сценариев. Это абсурд. Вы даже не можете условно указать typedef (если только вы не сбалансируете каждый typedef макросом, указывающим, что typedef установлен)!   -  person Dmitry    schedule 27.07.2017


Ответы (7)


Меня это удивило, потому что я совершенно уверен, что повторное объявление одного и того же typedef в той же области допустимо в C++, но, по-видимому, это недопустимо в C до стандарта 2011 года.

Во-первых, имена typedef не имеют связи:

ИСО/МЭК 9899:1999 + ТК3 6.2.6/6:

Следующие идентификаторы не имеют связи: идентификатор, объявленный чем-либо, кроме объекта или функции [...]

и 6,7/3:

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

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

Стандарт C 2011 года позволяет повторно объявлять имена typedef. 6.7 3 говорит:

… имя typedef может быть переопределено для обозначения того же типа, что и в настоящее время, при условии, что этот тип не является изменяемым типом;…

person CB Bailey    schedule 03.12.2011
comment
Значит ли это, что мне лучше просто включить заголовки в заголовки, чтобы об этом позаботились охранники включения? Не использовать структуру без typedef? - person BrT; 04.12.2011
comment
@BrT: Да, я так думаю. Это немного зависит от ваших целей. Если вам нужно использовать только имя typedef в качестве ссылки на неполный тип, вы можете использовать отдельный заголовок с объявлением typedef, как в ответе @Potatoswatter. Если вы просто использовали typedefs в качестве альтернативы #include и нет особых затрат на определение полной структуры в каждой TU, вы также можете сохранить #include и, при желании, сохранить объявление typedef с определением типа для согласованности. - person CB Bailey; 04.12.2011
comment
В программе, которую я сейчас делаю с C9, кажется, вы не можете включать заголовок с typedef более одного раза без ошибок. - person Matthew Mitchell; 01.05.2012
comment
Начиная с C11 (за 8 месяцев до этого ответа!) разрешено переопределять typedef для того же самого. Для этого случая в 6.7/3 добавлено исключение. - person M.M; 16.02.2017
comment
Я думаю, что то, что сказал @MM, должно быть включено в этот ответ. В противном случае это заблуждение. - person underscore_d; 21.05.2017
comment
@MM Это большая проблема для существующих кодовых баз. Для тех, кто ищет, -std=c11 - ваш ответ. - person akhan; 12.05.2020

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

// a_fwd.h

#ifndef A_FWD_H
#define A_FWD_H

typedef struct A_ A;

#endif

// a.h

#ifndef A_H
#define A_H

#include "a_fwd.h"

struct A_ {
};

#endif

Теперь всегда безопасно включать любые заголовки в любом порядке.


Незаконно иметь два определения чего-либо. Typedef — это определение, а не просто объявление, поэтому один компилятор был довольно слабым, чтобы допустить избыточность.

person Potatoswatter    schedule 03.12.2011
comment
Что касается вашего последнего утверждения, я думал, что вы можете иметь только определения объектов и функций в C и что объявление typedef было объявлением, которое вводило новое имя для существующего типа? - person CB Bailey; 04.12.2011
comment
@CharlesBailey C99, §6.7/5: определение идентификатора — это объявление для этого идентификатора, которое: … для константы перечисления или имени typedef является (единственным) объявлением идентификатора. - person Potatoswatter; 07.12.2011
comment
Поместите объявления вне токена защиты, а определения внутри. - person Calmarius; 16.12.2011
comment
@Calmarius: Никогда не размещайте ничего за пределами блока #ifndef … #endif, если вы это имеете в виду. - person Potatoswatter; 16.12.2011

Просто из соображений стиля я бы поставил typedef после структуры. то есть:

struct B_ {
    int c;
};
typedef struct B_ B;

Таким образом, вы говорите: «вот B_, и теперь я хочу называть его B». Может быть наоборот что-то в компиляторе дурит.

person Rob    schedule 03.12.2011
comment
Нет, порядок не имеет значения. - person Potatoswatter; 03.12.2011
comment
Нет ничего плохого в том, чтобы поставить typedef первым. Помещение на первое место говорит, что если вы видите B, это на самом деле struct B. Он повсеместно используется в плане 9 и очень удобен для связанных списков, позволяя typedef struct A A; struct A {int data; A *next}; - person Dave; 03.12.2011

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

//A.h
typedef struct A A;
struct A {
    double a;
    B* b;
};

// B.h
typedef struct B B;
struct B {
    int c;
};

// C.h
void function_do_something(A*, B*);

// C.c
#include "B.h"
#include "A.h"
#include "C.h"

void function_do_something(A* a, B* b){ ... }

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

person Dave    schedule 03.12.2011

Вы переопределяете A и B, записывая один и тот же оператор в несколько заголовочных файлов. Одним из решений было бы удалить typedef из A и B из A.h и B.h и использовать ваш C.h как есть.

person Jan Henke    schedule 03.12.2011

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

#ifndef __HEADER_H__
#define __HEADER_H__

// Your code goes here

#endif

Таким образом, ненужные включения будут пропущены этой блокировкой.
В вашем примере вам нужно будет включить B в A и A в C. Включение B в C не будет иметь никакого эффекта и удовлетворит компилятор.

person friendzis    schedule 03.12.2011
comment
OP специально не включает заголовки в заголовки. Это делает ненужным включение охранников. - person Dave; 03.12.2011
comment
Я не ставил вопрос, но все заголовки уже имеют охрану. - person BrT; 04.12.2011
comment
Тогда более уместно использовать объявления extern - person friendzis; 05.12.2011

Вы несколько раз определяете одно и то же.

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

Этот код работает:

#include <stdio.h>

typedef struct _B B;
typedef struct _A A;

struct _A {
    double a;
    B *b;
};

struct _B {
    int c;
};

void function_do_something(A* a, B* b)
{
    printf("a->a (%f) b->c (%d)\n", a->a, b->c);
}

int main()
{
   A a;
   B b;

  a.a = 3.4;
  b.c = 34;

  function_do_something(&a, &b);

  return 0;
}

Выход:

> ./x
a->a (3.400000) b->c (34)

EDIT: обновлено для C

РЕДАКТИРОВАТЬ 2: распространить на несколько заголовочных файлов

b.h:

#ifndef B_H
#define B_H

struct _B {
    int c;
};

#endif

a.h:

#ifndef A_H
#define A_H

typedef struct _B B;

struct _A {
    double a;
    B *b;
};

typedef struct _A A;

#endif

основной.с:

#include <stdio.h>

#include "a.h"
#include "b.h"

void function_do_something(A* a, B* b)
{
    printf("a->a (%f) b->c (%d)\n", a->a, b->c);
}

int main()
{
   A a;
   B b;

  a.a = 3.4;
  b.c = 34; 

  function_do_something(&a, &b);

  return 0;
}
person stefanB    schedule 03.12.2011
comment
это C, поэтому нет std::cout и т. д. - person hmjd; 03.12.2011
comment
Это, безусловно, не будет работать в C. Вам придется использовать typedef, иначе вы не сможете использовать A и B, как в коде. - person another.anon.coward; 03.12.2011
comment
Как бы работало решение с несколькими файлами, если бы функция function_do_something должна была находиться в отдельном файле? Наши, если A и B также использовались в другой функции в другом файле? - person BrT; 04.12.2011