26
votes

I am possibly doing this incorrectly and this is much a question about why it works in one compiler and not the other.

I have a large C application, and I am trying to follow the style of not including header files from within other header files. Instead, using forward declarations; thus I am trying the following.

// 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) {
    ...
}

This paradigm compiles and runs in Ubuntu 11.10 gcc -- but it gives compiler erros in OpenSUSE gcc that say "redefinition of typedef".

I have been doing my development in Ubunutu and so hadn't realised that this paradigm might be incorrect. Is it just that this is plain wrong and Ubuntu's gcc is being too nice?

7
@Charles sorry, C.c should also include A.h and B.h so that it knows about the struct contents - BrT
THIS is why i want to replace C preprocessor with php, or any other scripting language. This is absurd. You can't even conditionally typedef(unless you balance every typedef with a macro to indicate the typedef has been set)! - Dmitry

7 Answers

31
votes

I was surprised by this because I'm fairly sure that redeclaring the same typedef in the same scope is legal in C++, but apparently it is not legal in C prior to the 2011 standard.

First, typedef names have no linkage:

ISO/IEC 9899:1999 + TC3 6.2.2/6 (Linkages of identifiers):

The following identifiers have no linkage: an identifier declared to be anything other than an object or a function [...]

and 6.7/3 (Declarations):

If an identifier has no linkage, there shall be no more than one declaration of the identifier (in a declarator or type specifier) with the same scope and in the same name space, except for tags as specified in 6.7.2.3.

So you need to ensure that each typedef declaration appears only once at file scope in each translation unit.

The 2011 C standard allows redeclaration of typedef names. 6.7 3 says:

… a typedef name may be redefined to denote the same type as it currently does, provided that type is not a variably modified type;…

6
votes

One piece of the idiom is missing. The forward declarations are independent from the definitions, so they should be in a separate header file.

// 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

Now it's always safe to include any headers in any order.


It is illegal to have two definitions of anything. A typedef is a definition, not just a declaration, so the one compiler was being quite lax to allow the redundancy.

1
votes

Just as a matter of style I would put the typedef after the struct. i.e.:

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

That way you are saying: "here is B_ and now I want to refer to it as B". It could be that the other way around fools something in the compiler.

1
votes

The ubuntu compiler is being overly soft; you can't typedef the same thing twice. In the style you refer to, the ordering of the includes is important, and is usually mentioned as a comment in the header file, or in the documentation. In this case, you would have:

//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){ ... }

You might note that this will get messy in the case of cyclic dependancies.

0
votes

You are redefining A and B by writing the same statement in several header files. One solution would be to drop the typedef of A and B from the A.h and B.h and use your C.h as it is.

0
votes

As others have already stated, you cannot redefine types in C. This error mostly indicates that there might be looped includes or other logic flaw. To avoid this, the best practice is to lock include files i.e.

#ifndef __HEADER_H__
#define __HEADER_H__

// Your code goes here

#endif

This way unnecessary includes will be omitted by this lock.
In your example you would need to include B in A and A in C. Including B in C would have no effect and would satisfy the compiler

-2
votes

You are defining multiple times the same thing.

You can spread it across multiple header files just have to make sure that there is some B seen before struct _A is defined.

This code works:

#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;
}

Output:

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

EDIT: updated for C

EDIT 2: spread into multiple header files

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

main.c:

#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;
}