0
votes

I'm trying to forward declare a class with the _may_alias attribute, but GCC gives an error when I attempt to do so:

struct __attribute__((__may_alias__)) MyType;
MyType* foo();

typedef struct  __attribute__((__may_alias__)) MyType { ... } MyType;
MyType* foo() {}

Gives the error: testc.c:4: error: redefinition of typedef ‘A’
testc.c:1: note: previous declaration of ‘A’ was here
testc.c:5: error: conflicting types for ‘foo’
testc.c:2: note: previous declaration of ‘foo’ was here

Is there a way to do this?

2

2 Answers

2
votes

C doesn't allow to do a typedef twice. In addition you have to distinguish between the forward declaration of a struct and of a typedef. The easiest way to have that is to use the same token as struct tag and as a typedef identifier. Without the attribute stuff, in standard C this would look like:

/* this is a forward declaration of struct and typedef */
typedef struct MyType MyType;
MyType* foo(void);

/* declare the struct as a struct */
struct MyType { };
MyType* foo(void) { return NULL; }

Now comes the play with the attributes. You'd have to find out to which it applies to the struct declaration or the typedef. My guess is for the struct, but a quick look in to the gcc info should show you that.

/* this is a forward declaration of struct and typedef */
typedef __attribute__((__may_alias__)) struct MyType MyType;

/* declare the struct as a struct */
__attribute__((__may_alias__)) struct MyType { };
0
votes

After hours of experiments, it seems to be a limitation bug of GCC. The newer GCCs do not have this issue.

1. Forward declaration, no may_alias (wrong behavior)

Here's a minimal demonstration of the strict aliasing problem:

#include <stdio.h>

struct MyType;  // Forward declaration here, without may_alias.

void foo(struct MyType *, int *b);

struct MyType {
    short a;
};  // Full definition here, without may_alias.

void f(struct MyType *my_type, int *b)
{
    *b = 1;
    my_type->a = 0;

    if (*b == 1) {
        printf("Strict aliasing problem\n");
    }
}

int main(void) {
    int b;
    f((struct MyType *)&b, &b);

    return 0;
}

Compile it with GCC 5.4.0:

$ gcc -O2 -o main main.c
$ ./main
Strict aliasing problem

2. No forward declaration, no may_alias (wrong behavior)

Same as above.

3. Forward declaration, may_alias (won't compile)

struct __attribute__((may_alias)) MyType;  // Forward declaration here, with may_alias.

struct __attribute__((may_alias)) MyType {
    short a;
};  // Full definition here, with may_alias.

Compile it with GCC 5.4.0:

$ gcc -O2 -o main main.c
main.c:11:10: error: conflicting types for ‘foo’
     void foo(struct MyType *my_type, int *b)
          ^
main.c:5:10: note: previous declaration of ‘foo’ was here
     void foo(struct MyType *, int *b);

Seems like GCC thinks struct MyType; is a different type. However, I didn't find anyway to add may_alias attribute to a forward declaration. According to the order doc:

It is ignored if the content of the structure, union or enumerated type is not defined in the specifier in which the attribute specifier list is used—that is, in usages such as struct attribute((foo)) bar with no following opening brace.

A potential workaround would be declaring foo like this:

void foo(struct MyType __attribute__((may_alias)) *, int *b);

However, this is not a good solution, seems like this syntax might be not yet supported:

Note again that this does not work with most attributes; for example, the usage of ‘aligned’ and ‘noreturn’ attributes given above is not yet supported.

And although it compiles, the may_alias attribute doesn't work:

$ gcc -O2 -o main main.c
$ ./main
Strict aliasing problem

4. No forward declaration, may_alias (good)

This is the only way that works.

$ gcc -O2 -o main main.c
$ ./main
$

However, in my case, forward declaration is required. My workaround is to use void * instead of struct MyType *:

// In the .h file, no definition of MyType yet.
void foo(void *my_type, int *b);

// In the .c file, has the definition of MyType.
void foo(void *t, int *b)
{
    struct MyType *my_type = t;

    // ...
}

This is not elegant at all, but is the only way that works at the moment.