0

I have come across code like this multiple times and can't seem to grasp the idea or necessity behind it. I am referring to why does some code declare a struct of said name then they use typedef then declare the struct again and only add the contents of the struct? An example is below:

struct foo;
typedef struct foo *foobar;

struct foo {
    void *data;
    foobar example[];
};

Isn't this declaration slightly unnecessary, couldn't it be as follows?

typedef struct foo *foobar;

struct foo {
    void *data;
    foobar example[];
};
Secernere
  • 63
  • 1
  • 1
  • 9

3 Answers3

3

You need to define a typedef before you can reference it. (Additionally, there is a school of thought that discourages the use of pointer typedefs, but that is strictly a convention.)

Typically one will see a "forward" typedef for a struct in cases where the struct itself contains pointers to other instances of itself, or more generally sets of structs that mutually reference each other. For example:

typedef struct foo_s foo;
struct foo_s {
    int val;
    foo *next;
};

You could, of course, always use the structure tag in the structure definition, i.e. struct foo_s, but if the desire is to use the typedef foo consistently, then it needs to be defined before the structure is defined.

Tom Karzes
  • 22,815
  • 2
  • 22
  • 41
1

They are not the same. Your first suggestion,

struct foo {
    void *data;
    foobar example[];
};

typedef struct foo *foobar;

yields:

error: unknown type name 'foobar'
     foobar example[];
     ^

and your second,

typedef struct foo {
    void *data;
    foobar example[];
} *foobar;

yields:

error: unknown type name 'foobar'
     foobar example[];
     ^

You have to declare before you define. The original way works because both

struct foo;

and

typedef struct foo *foobar;

are declarations that are not definitions. Such "non-definitional declarations" need to be employed to make mutually-recursive name references work.

As pointed out in the comments (thanks @melpomene) the struct foo; declaration is actually not required.

Ray Toal
  • 86,166
  • 18
  • 182
  • 232
  • The original version works because `typedef struct foo *foobar;` appears before the struct definition. The `struct foo;` line is actually redundant. – melpomene Oct 23 '17 at 05:20
1

The one time the long-winded notation matters is where the sequence is inside a function and there's a type struct foo defined outside the function too. Then the plain struct foo; line shadows the out-of-function definition. If you don't include that line, you can end up with incompatible pointer types, both called foobar, in structures that look the same, leading to horrible confusion. (See also Which part of the C standard allows this code to compile? and Does the C standard consider that there are one or two 'struct uperms_entry' types in this header? for some more examples.)

What? You're confused by what I said? OK; here comes an example. Note this is contrived; it is bad style to redefine a structure type inside a function (and the example of the problem depends on you doing so).

#include <stdlib.h>

struct foo;      // This line is optional; it is harmless if omitted
typedef struct foo *foobar;

struct foo
{
    void *data;
    foobar example[];  // Flexible array member; array of pointers.
};

extern void some_function(void);

void some_function(void)
{
    struct foo;     // This line is crucial!
    typedef struct foo *foobar;

    struct foo
    {
        foobar *array[3];
        void *pointer;
    };
    foobar x = calloc(sizeof(*x), 1);
    foobar y = malloc(sizeof(*y));
    y->array[0] = x->array[1];
    y->array[1] = x->array[2];
    y->array[2] = x->array[0];
    y->pointer  = x->array[1];

    free(x);
    free(y);
}

With the struct foo; line in the function, the code compiles cleanly:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes -pedantic -c st97.c
$

The struct foo; line inside the function says "forget about any previous struct foo type; there's a new one in this scope". The forgetfulness only lasts for the scope, of course.

With the struct foo; line commented out in the function, the code fails to compile noisily:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes \
>     -Wstrict-prototypes -pedantic -c st97.c
st97.c: In function ‘some_function’:
st97.c:28:6: error: ‘struct foo’ has no member named ‘array’
     y->array[0] = x->array[1];
      ^~
st97.c:28:20: error: ‘struct foo’ has no member named ‘array’
     y->array[0] = x->array[1];
                    ^~
st97.c:29:6: error: ‘struct foo’ has no member named ‘array’
     y->array[1] = x->array[2];
      ^~
st97.c:29:20: error: ‘struct foo’ has no member named ‘array’
     y->array[1] = x->array[2];
                    ^~
st97.c:30:6: error: ‘struct foo’ has no member named ‘array’
     y->array[2] = x->array[0];
      ^~
st97.c:30:20: error: ‘struct foo’ has no member named ‘array’
     y->array[2] = x->array[0];
                    ^~
st97.c:31:6: error: ‘struct foo’ has no member named ‘pointer’
     y->pointer  = x->array[1];
      ^~
st97.c:31:20: error: ‘struct foo’ has no member named ‘array’
     y->pointer  = x->array[1];
                    ^~
$

At the point when the type foobar is defined inside the function, the name struct foo refers to the type defined outside the function, so the structure contains an array of pointers to pointers to the external struct foo, and that structure has members data and example and not members array and pointer.

As I said, this is appalling style. So, you say "what about if I use struct foo inside the structure definition in the function:

#include <stdlib.h>

struct foo;
typedef struct foo *foobar;

struct foo
{
    void *data;
    foobar example[];  // Flexible array member; array of pointers.
};

extern void some_function(void);

void some_function(void)
{
    //struct foo;
    typedef struct foo *foobar;

    struct foo
    {
        struct foo *array[3];
        void *pointer;
    };
    foobar x = calloc(sizeof(*x), 1);
    foobar y = malloc(sizeof(*y));
    y->array[0] = x->array[1];
    y->array[1] = x->array[2];
    y->array[2] = x->array[0];
    y->pointer  = x->array[1];

    free(x);
    free(y);
}

This is still a problem. The internal struct foo is not complete until the }, so the struct foo *example[3] still refers to the external struct foo — and you get the same error messages.

The short moral is "do not redefine a structure type inside a function".

If you must do so, note that it can only really be used in the function; it is hard to pass it to other functions — not quite impossible, but you really have to know what you're doing.

And the longer moral is "occasionally, under dubious circumstances, the struct foo; line on its own can be crucial".

Note that even if the structure body definitions are actually the same, technically, they're different types (both called struct foo though). This is confusing to everyone — don't do it, please! Actually, let's make that more imperative:

Don't do it!

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278